Showing posts with label javascript. Show all posts
Showing posts with label javascript. Show all posts

Saturday, January 16, 2021

Virus Infection Simulator

Was talking to my S-I-L about some school projects and an idea for this came up. Out of scope from what her project required, but in scope for something to do on a day off!

Direct link to github html preview.

I started by defining a list of "cities" - x, y coordinates are simply distributed in a circle around the center of the screen.

People is an array of persons. A person is in a city (thus having that city's x, y location as a base point. The person wanders based on sin/cos curves.

e.g. lets say the person wanders around the city. Cities are arranged in a circle. we can describe a function given a time to determine the location of a person as they wander the city.

It's  not hard to think this through. A "person" will need some stats. Direction, speed, wandering constants, home city. "Cities" will have constants too - size, location. We can consider population density. Maybe some cities have mandates that reduce infection rate.

//Basic wandering idea... 

//lets give a person a random direction, clockwise or anti

persondirection=random()-0.5

//wander factor is how often the person will head to the center of a town while they run around.

wanderfactor=random()*4+2

//Given a number of cities, we can distribute cities in a circle.

cityx=sin(2*pi*personcitynumber/citycount)*cityspread

cityy=cos(2*pi*personcitynumber/citycount)*cityspread

//lets have the person walk in a circle in the direction/speed of their random pick. Time is a variable we can increment every frame. Offset is a random constant per person. It's so that that everyone doesn't seem to start at the same point at the same time.

wanderingx=sin(time*persondirection+offset)*citysize

wanderingy=cos(time*persondirection+offset)*citysize

//we can make the person wander a little more than just in a perfect circle. Sin/2 is -0.5 to 0.5. Add 0.5 and it's 0 to 1. 

centervisit=0.5+sin(time*wanderfactor+offset)/2

//add the city coordinates with the wandering x,y multiplied by the wander factor, and they'll be moving around a city location, moving to the center and back out to their normal ring around the city.

personx cityx centervisit wanderingx

persony cityy centervisit wanderingy

Fleshed out a little more, the wandering patterns looked pretty decent. Throw these values in an object per person, make an array of people, and city variables, and add some more properties like what city the person is moving to, and progress there.

See the source here

https://github.com/beomagi/html-tinkers/blob/master/virus-sim/virus-sim.html

Edit: added some color, event log and graph. See the git repo for future changes.

Thought: should make this an interactive game. Cities can have money saved that decreases if under lockdown. People can have a happiness factor that depends on safeguards and how they are doing in relation to other cities... 

Sunday, April 12, 2015

Mandelbrot Set Rendering

Benoit Mandelbrot was a Polish-born mathematician. His most well known work is in the field of fractals. He came up with a function describing a set of complex numbers called the Mandelbrot Set.
The Mandlebrot set describes a set of numbers "c" such that the sequence (c1, c2 .... cx) where cn+1 = (cn*cn)+cn does not approach infinity.
So you're looking at c, c²+c, (c²+c)²+c, ((c²+c)²+c)²+c...

A complex number has two parts. It's a sum of real an imaginary components. Let's say we're looking at A + Bi.
A is the real component.
Bi is the imaginary component.
'i' is a symbol representing the √-1 (square root of -1)
i.e., (Bi)² would be -B².

Mandelbrot's equation for describing the model is z'=z²+c.
Let z be our A + Bi from above.
The next element of the sequence is 
If z=(A+Bi)² then z=A²-B²+2*A*Bi


If plotting, we can us X, Y in place of A, and B.
so, z=X²-Y²+2*X*Y(i)
We have real and imaginary components here.
So Xnew=X²-Y², and Ynew=2*X*Y
the "c" is the starting point for Mandelbrot's equation.
Z=Z²+c, so we need to add X0 and Y0 to the above:

Xnew=X²-Y²+X0
Ynew=2*X*Y+Y0
And from here, we keep iterating on this till we hit set limits to number of iterations, or if X/Y are sufficiently large.

So, for a point X, Y,
Repeat the following until x0²+y0²>4 or until set max iterations

Until (max iterations, or x0²+y0²>4)
  x0=0 and y0=0
  x1=x0*x0-y0*y0+X
  y1=2*x0*y0+Y
  x0=x1
  y0=y1
Color point X,Y based on iterations.




Point X,Y is then associated with the number of iterations to break out of that loop.

Using this we can construct a basic Mandelbrot generator. I did this using Javascript, since it's easy to use any browser to display a canvas element. This works in Firefox and Chrome (has worked for a while) and I've tested it in IE11 and it works there too.




<!DOCTYPE HTML>
<html>
<script language="javascript">
  function rgbToHex(r, g, b) { //thanks stackoverflow
    return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
  }
  function putpixel(x,y,r,g,b,ctx){
    ctx.fillStyle = rgbToHex(r,g,b);
    ctx.fillRect(x,y,1,1);
  }
  function paint(cnvname){
    mcanvas = document.getElementById(cnvname);
    ctx = mcanvas.getContext('2d');
    ctx.clearRect(0, 0, 799, 599);
    for (x = 0; x < 799; x++) {
      for (y = 0; y < 599; y++) {
        tx=(x-400)/(200); // tx is "translated x" for graph centering"
        ty=(300-y)/(200); // ty is "translated y" for graph centering"
        xtmp=0;
        ytmp=0;
        xtmp2=0;
        iter=0
        maxiter=1000;
        for (; (iter<maxiter) && (xtmp*xtmp+ytmp*ytmp < 4);) {
          xtmp2=xtmp*xtmp-ytmp*ytmp+tx;
          ytmp=2*xtmp*ytmp+ty;
          xtmp=xtmp2;
          iter++;
        }
        contrast=0.15
        color=Math.floor( (Math.pow(iter,contrast)*255)/Math.pow(maxiter,contrast));
        putpixel(x,y,0,255-color,255-color,ctx);
      }
    }
  }
</script>
<body onload='paint("mcanvas")'>
  <div>
    <canvas id="mcanvas" width="800" height="600"></canvas>
  </div>
</body>
</html>


This is a simple bit of javascript that iterates over the pixels of the canvas created in the body. It applies the function described earlier to determine the max number of iterations to the set.



Pretty right? Of course there's much more to mandelbrot than just the bulb.
We can zoom in on a portion of this by applying a zoom and x/y offset to the above code.

We can add a zoom and offset feature to explore the set.


  for (x = 0; x < 799; x++) {
    for (y = 0; y < 599; y++) {
      zoom=20; xoff=0.6; yoff=0.6;
      tx=-xoff+(x-400)/(zoom*200); // tx is "translated x" for graph centering"
      ty=yoff+(300-y)/(zoom*200); // ty is "translated y" for graph centering"

The above change will produce the following:

This is zoomed in 20x onto the small bulb in the top left portion of the largest bulb. Note the pattern replicates, and there are several tiny bulbs that look a lot like the main one (with slight variations).

Now the fun part - we can color based on x, y or iterations. Apply a sinusoidal function to iterations for red, blue and green, and you can have a never ending palette of color.

e.g.
From the contrast and color line:

//contrast=0.25
//color=Math.floor( (Math.pow(iter,contrast)*255)/Math.pow(maxiter,contrast));
cycle=(maxiter)/(3.14159265359*100);
cr=Math.floor(128+127*Math.sin(0.00+iter/(cycle*1.5000)));
cg=Math.floor(128+127*Math.sin(1.57+iter/(cycle*1.6000)));
cb=Math.floor(128+127*Math.sin(3.14+iter/(cycle*1.5666)));
putpixel(x,y,cr,cg,cb,ctx);

So, what are we saying? A cycle is 2*pi. We're dividing the iterations by 100*pi, so we're looking at at 50 cycles. Red, green and blue values are sinusoidal cycles with different start points - red starts at 128+127*sin(0) (so 128, and increasing). Green is 128+127*sin(1.57) (so 255, 1.57 being half pi, sin(pi/2) is 1) - i.e. max green and heading down to 0. Blue is starting at 128+sin(pi), which means middle intensity and getting darker.
Each sinusoidal cycle has it's own multiplier so each cycle has it's own rate, giving many more colors ans each cycle creates a new palette.

Here's the result:


We're looking at integers since we're only counting max iterations. We can smoothen this by looking at how far past our boundary condition each iteration went.

My script was based on methods described in wikipedia.


log2=Math.log(2)
if (iter<maxiter){ //smoother transitions
  zn=Math.sqrt(xtmp*xtmp+ytmp*ytmp);
  zl=Math.log(Math.log(zn)/log2)/log2;
  iter+=1-zl;
} 






Now consider the interior of the bulb. It's what happens when the iterations have been maxed. We can color the interior by using the same palette. I haven't quite figured out the how to map distance, but we can use the existing xtmp and ytmp variables. Since iterating more doesn't get us greater than the boundary condition, we can assume xtmp and ytmp are small. 

What I tried was several permutations of setting iter to 1/xtmp and 1/ytmp. I settled on using iter=Math.log( (1/(xtmp*xtmp+ytmp*ytmp)) )/(log2);
The idea is that if my values aren't going to break out of the boundary, then they're converging. So I'm using the inverse of the values to dictate what they will be against the existing palette. Not all sub-bulbs show this though.


Here's the overall code for this basic mandelbrot renderer...



<!DOCTYPE HTML>
<html>
<script language="javascript">
  function rgbToHex(r, g, b) { //thanks stackoverflow
    return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
  }
  function putpixel(x,y,r,g,b,ctx){
    ctx.fillStyle = rgbToHex(r,g,b);
    ctx.fillRect(x,y,1,1);
  }
  function paint(cnvname){
    mcanvas = document.getElementById(cnvname);
    ctx = mcanvas.getContext('2d');
    ctx.clearRect(0, 0, 799, 599);
    log2=Math.log(2)
    for (x = 0; x < 799; x++) {
      for (y = 0; y < 599; y++) {
        zoom=20; xoff=0.6; yoff=0.6;
        tx=-xoff+(x-400)/(zoom*200); // tx is "translated x" for graph centering"
        ty=yoff+(300-y)/(zoom*200); // ty is "translated y" for graph centering"
        xtmp=0;
        ytmp=0;
        xtmp2=0;
        iter=0
        maxiter=1000; // increase with larger zoom values
        totes=0;
        for (; (iter<maxiter) && (xtmp*xtmp+ytmp*ytmp < 4);) {
          xtmp2=xtmp*xtmp-ytmp*ytmp+tx;
          ytmp=2*xtmp*ytmp+ty;
          xtmp=xtmp2;
          iter++;
        }
        if (iter<maxiter){ //smoother transitions
          zn=Math.sqrt(xtmp*xtmp+ytmp*ytmp);
          zl=Math.log(Math.log(zn)/log2)/log2;
          iter+=1-zl;
        } else { // for internal color of Mandelbulb
          iter=Math.log( (1/(xtmp*xtmp+ytmp*ytmp)) )/(log2);
        }
        cycle=(maxiter)/(3.14159265359*100); //50 cycles from 0-maxiter
        cr=Math.floor(128+127*Math.sin(0.00+iter/(cycle*1.5000)));
        cg=Math.floor(128+127*Math.sin(1.57+iter/(cycle*1.6000)));
        cb=Math.floor(128+127*Math.sin(3.14+iter/(cycle*1.5666)));
        putpixel(x,y,cr,cg,cb,ctx);
      }
    }
  }
</script>
<body onload='paint("mcanvas")'>
  <div>
    <canvas id="mcanvas" width="800" height="600"></canvas>
  </div>
</body>
</html>


Manipulation of the color palette constants, zoom, offsets, and even the equation can produce some beautiul results.

For example,
Set the initial xtmp to tx and ytmp to ty.

change:
xtmp2=xtmp*xtmp-ytmp*ytmp+tx;
ytmp=2*xtmp*ytmp+ty;


to:
xtmp2=xtmp*xtmp-ytmp*ytmp-0.32;
ytmp=2*xtmp*ytmp+0.6575;


Now you're making a Julia plot.
This is another fractal you can zoom in continuously.

Let's look at zooming in on a portion of the Mandelbrot set.
zoom=1.00; xoff=0; yoff=0;


zoom=5; xoff=0.7348; yoff=0.1798;

zoom=25; xoff=0.8; yoff=0.2;



zoom=125; xoff=0.79; yoff=0.158;


zoom=625; xoff=0.79; yoff=0.162;

zoom=3125; xoff=0.7893; yoff=0.163;


At this point we need to increase the maxiter constant. I raised this to 10000.
zoom=9125; xoff=0.78937; yoff=0.16307;


 zoom=15625; xoff=0.78940; yoff=0.163055;


zoom=78225; xoff=0.78940; yoff=0.163055;
Note the zoom - we're at almost 80000x and as you can see there's a lot more structures here!

Wednesday, August 7, 2013

The HTML/Javascript makeup of the animated Bubble Sort

Below is the HTML and Javascript source for the animated bubble-sort animation.


Animation in Javascript is a bit annoying.


Let's say I want to show an object moving from point ax,ay to point bx,by in 100 steps.


I'd normally expect something like this:


For (c=0;c<=100;c++){
   cx=ax+(c/100)*(bx-ax)
   cy=ay+(c/100)*(by-ay)
   object.move(cx,cy)
   delay(frametime)
}


Instead javascript needs something more like:


function moveobject_ani(ax,ay,bx,by,c){
   cx=ax+(c/100)*(bx-ax)
   cy=ay+(c/100)*(by-ay)
   object.move(cx,cy)
   if c<100 {
      setTimeout('moveobject_ani(ax,ay,bx,by,c+1)',delay)
   }
}

NB: About the equation - cx=ax+(c/100)*(ax-bx)
When c=0, the red portion of cx=ax+(c/100)*(bx-ax) is 0. therefore, cx starts at ax.
When c=100, it becomes cx=ax+(100/100)*(bx-ax). The equation becomes cx=ax+(bx-ax). i.e. cx=bx.

C:\Documents and Settings\ryan.cipriani\My Documents\scripts\bubblesort.html.html
<html>
  <head>
    <title>
      BubbleSort
    </title>
    <style>
      #resetit {
        font-size:1.2em;
        padding:4px;
        background-color:#cdcdcd;
        width:500px;
        box-sizing:border-box;
        -moz-box-sizing:border-box;
        -webkit-box-sizing:border-box;
        position:relative;
        cursor:pointer;
        font-family:"Courier", "Terminal", Monospace;
      }
      #scratchboard {
        font-size:0.8em;
        padding:4px;
        background-color:#dfefdf;
        color:#002200;
        width:500px;
        height:450px;
        box-sizing:border-box;
        -moz-box-sizing:border-box;
        -webkit-box-sizing:border-box;
        position:relative;
        cursor:pointer;
        font-family:"Courier", "Terminal", Monospace;
      }
      .typ1{
        font-size:1.2em;
        color:#007700;
        padding:4px;
        border-color:#222222;
        border-style:solid;
        border-width:1px;
        font-weight:bold;
        position:absolute;
        width:180;
        font-family:"Courier", "Terminal", Monospace;
      }
      #smallest{
        font-size:1.2em;
        color:#bb0000;
        padding:4px;
        font-weight:bold;
        position:absolute;
        width:55px;
        Left:50px;
        Top:20px;
        font-family:"Courier", "Terminal", Monospace;
      }
      #curcompare{
        font-size:1.2em;
        color:#0000bb;
        padding:4px;
        font-weight:bold;
        position:absolute;
        width:55px;
        Left:360px;
        Top:20px;
        font-family:"Courier", "Terminal", Monospace;
      }
    </style>

    <script language="Javascript">
      function $(id){return document.getElementById(id);}

      var elements=["watermelon", "orange", "pear", "kumquat", "apricot", "banana", "apple", "squash", "cantalope"]
      function make_number_list(){
        current=""
        $('scratchboard').innerHTML="Click to sort...<div id='smallest'>Smallest=&gt;</div><div id='curcompare'>&lt;=Checking</div>"
        for (i=0;i<elements.length;i++)
        {
          numpackage="<div class='typ1' id='fele_"+i+"'>"+elements[i]+"</div>"
          current=$('scratchboard').innerHTML;
          $('scratchboard').innerHTML=current+numpackage
          $('fele_'+i).style.top = 20+i * 40;
          $('fele_'+i).style.left = '160px';
        }
      }
      function resetalpha(){
        elements=["watermelon", "orange", "pear", "kumquat", "apricot", "banana", "apple", "squash", "cantalope"];
        make_number_list();
      }

      funk=""
      function moveboxes(box1, b1top, mainx, box2, b2top, mainx,iter,idx1,idx2) {
        totaliter = 30
        turnrad=Math.round((b2top-b1top)*Math.sin((Math.PI*iter*0.5)/totaliter ));
        box1newtop=b1top+turnrad;
        box2newtop=b2top-turnrad;
        $(box1).style.top=box1newtop+'px';
        $(box2).style.top=box2newtop+'px';
        s=Math.round((b2top-b1top)*Math.sin( (Math.PI*iter)/totaliter ));
        $(box1).style.left=Math.round(mainx+(s*0.4))+'px';
        $(box2).style.left=Math.round(mainx-(s*0.3))+'px';
        if (iter < totaliter) {
          setTimeout('moveboxes(box1, b1top, mainx, box2, b2top, mainx,'+(iter+1)+','+idx1+','+idx2+')',(1000/120));
        } else {
          swaptmp=elements[idx1]
          elements[idx1]=elements[idx2]
          elements[idx2]=swaptmp
          make_number_list()
          $('smallest').style.top = (20+idx1 * 40)+'px';
          $('curcompare').style.top = (20+idx2 * 40)+'px';
          setTimeout(funk,50);
        }
      }
      function swap(ia,ib){
        box1='fele_'+ia
        box2='fele_'+ib
        b1top=parseInt(($(box1).style.top).replace('px', ''));
        b2top=parseInt(($(box2).style.top).replace('px', ''));
        mainx=parseInt(($(box2).style.left).replace('px', ''));
        moveboxes(box1, b1top, mainx, box2, b2top, mainx,0,ia,ib)
      }

      function bubblestate(min, max, aidx, bidx){
        funk='bubblestate('+min+', '+max+', '+aidx+', '+bidx+')'
        $('smallest').style.top = (20+aidx * 40)+'px';
        $('curcompare').style.top = (20+bidx * 40)+'px';
        if (elements[aidx] > elements[bidx]) {
          swap(aidx, bidx)
          return
        } else {
          if (bidx == max) {
            aidx=aidx+1
            bidx=aidx+1
          } else {
            bidx +=1
          }
          if (aidx<max) {
            setTimeout('bubblestate('+min+', '+max+', '+aidx+', '+bidx+')',0);
          }
        }
      }

      function sortitall(){
        $('scratchboard').innerHTML=""
        make_number_list();
        bubblestate(0,elements.length-1,0,1)
      }
    </script>

  </head>
  <body onload="make_number_list()">
    <div id="resetit" onclick="resetalpha()">
      Reset Order...
    </div>
    <div id="scratchboard" onclick="sortitall()">
    </div>
  </body>
</html>