FlyingChaos

A chaotic "Orbit-Style" Circle

Log in to post a comment.

// You can find the Turtle API reference here: https://turtletoy.net/syntax
Canvas.setpenopacity(0.3);

const nRevs = 600; // How many revolutions should be plotted
const mean_height = 60;
const pointsPerRev = 100; // How many points should be plotted per full revolution
const heightFreq = 4; // How often the height should be updated per revolution
const heightScale = 12;
const headingFreq = 1
const headingScale = 0.1

// Global code will be evaluated once.
const turtle = new Turtle();

let height = mean_height;


let position = {
    x: 0,
    y: 0,
    z: 1
}

let heading = {
    x: 1,
    y: 0,
    z: 0
}

function crossProduct(v1, v2) {
    return {
      x: v1.y * v2.z - v1.z * v2.y,
      y: v1.z * v2.x - v1.x * v2.z,
      z: v1.x * v2.y - v1.y * v2.x
    }
}

function normalize(vec) {
    const n = Math.sqrt(vec.x**2 + vec.y**2 + vec.z**2)
    return {
        x: vec.x/n,
        y: vec.y/n,
        z: vec.z/n
    }
}

function rotate(pos, vec, angle) {
    
    const n1 = vec.x;
    const n2 = vec.y;
    const n3 = vec.z;
    
    const cosa = Math.cos(angle);
    const sina = Math.sin(angle);

    const Axx = n1**2*(1-cosa) + cosa;
    const Axy = n1*n2*(1-cosa) - n3*sina;
    const Axz = n1*n3*(1-cosa) + n2*sina;

    const Ayx = n2*n1*(1-cosa) + n3*sina;
    const Ayy = n2**2*(1-cosa) + cosa;
    const Ayz = n2*n3*(1-cosa) - n1*sina;

    const Azx = n3*n1*(1-cosa) - n2*sina;
    const Azy = n3*n2*(1-cosa) + n1*sina;
    const Azz = n3**2*(1-cosa) + cosa;
    
    const px = pos.x;
    const py = pos.y;
    const pz = pos.z;

    pos.x = Axx*px + Axy*py + Axz*pz;
    pos.y = Ayx*px + Ayy*py + Ayz*pz;
    pos.z = Azx*px + Azy*py + Azz*pz;
    
    return pos;
}

function gotoPosition(pos, height) {
    turtle.goto(pos.x*height, pos.z*height);
}

const heightSlopeAt = [];
for (let i = 0; i < nRevs*heightFreq+2; i++) {
  heightSlopeAt[i] = (Math.random()*2)-1;
}

const headingSlopeAt = [];
for (let i = 0; i < nRevs*headingFreq+2; i++) {
  headingSlopeAt[i] = (Math.random()*2)-1;
}

function samplePerlin(x, slopeAt) {
  const lo = Math.floor(x);
  const hi = lo+1;
  const dist = x-lo;
  loSlope = slopeAt[lo];
  hiSlope = slopeAt[hi];
  loPos = loSlope * dist;
  hiPos = -hiSlope * (1-dist);
  const u = dist * dist * (3.0 - 2.0 * dist);  // cubic curve
  return (loPos*(1-u)) + (hiPos*u);  // interpolate
}


turtle.penup();
gotoPosition(position, height);
turtle.pendown();

// The walk function will be called until it returns false.
function walk(i) {
    
    let rotationVec = crossProduct(position, heading);

    // Advance point in heading direction
    position = normalize(rotate(position, rotationVec, 2*Math.PI/pointsPerRev));
    heading = normalize(rotate(heading, rotationVec, 2*Math.PI/pointsPerRev));
    
    
    const headingInc = samplePerlin(i/pointsPerRev*heightFreq, heightSlopeAt)*headingScale
    heading = rotate(heading, position, headingInc);
    
    
    height = mean_height + (samplePerlin(i/pointsPerRev*heightFreq, heightSlopeAt)*heightScale)**2;
    
    gotoPosition(position, height);
    return i < nRevs*pointsPerRev;
}