Additive Perlin Noise

Adding the angle of the gradient vector to the turtle's heading at (x, y) instead of simply using it to set the turtle's heading.

Log in to post a comment.

let turtle_walk_distance = 2; //min=0.25, max=10, step=0.25
let noise_scale_denominator = 100; //min=5, max=500, step=1
let random_jump_chance = 0.02; //min=0.0, max=0.1, step=0.0005
let max_iterations = 100000; //min=10000, max=1000000, step=10000
let smoothstep_polynomial_order = 2 //min=1, max=10, step=1

const noise_scale = 1 / noise_scale_denominator;

// Joe Iddon's Perlin Noise (https://github.com/joeiddon/perlin)

const perlin = {
    rand_vect: function(){
        let theta = Math.random() * 2 * Math.PI;
        return {x: Math.cos(theta), y: Math.sin(theta)};
    },
    dot_prod_grid: function(x, y, vx, vy){
        let g_vect;
        let d_vect = {x: x - vx, y: y - vy};
        if (this.gradients[[vx,vy]]){
            g_vect = this.gradients[[vx,vy]];
        } else {
            g_vect = this.rand_vect();
            this.gradients[[vx, vy]] = g_vect;
        }
        return d_vect.x * g_vect.x + d_vect.y * g_vect.y;
    },
    smootherstep: function(x){
        return generalized_smooth_step(smoothstep_polynomial_order, x)
    },
    interp: function(x, a, b){
        return a + this.smootherstep(x) * (b-a);
    },
    seed: function(){
        this.gradients = {};
        this.memory = {};
    },
    get: function(x, y) {
        if (this.memory.hasOwnProperty([x,y]))
            return this.memory[[x,y]];
        let xf = Math.floor(x);
        let yf = Math.floor(y);
        //interpolate
        let tl = this.dot_prod_grid(x, y, xf,   yf);
        let tr = this.dot_prod_grid(x, y, xf+1, yf);
        let bl = this.dot_prod_grid(x, y, xf,   yf+1);
        let br = this.dot_prod_grid(x, y, xf+1, yf+1);
        let xt = this.interp(x-xf, tl, tr);
        let xb = this.interp(x-xf, bl, br);
        let v = this.interp(y-yf, xt, xb);
        this.memory[[x,y]] = v;
        return v;
    }
}

perlin.seed();

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

// Global code will be evaluated once.
const t = new Turtle();
t.penup();
t.goto(-50,-20);
t.pendown();

// The walk function will be called until it returns false.
function walk(i) {
    let current = {
        x: t.x(),
        y: t.y()
    }
    let n = perlin.get((current.x - 100) * noise_scale, (current.y - 100) * noise_scale);
    let h = Math.PI * 2 * n;
    let h_old = t.h() * Math.PI / 180;
    let h_new = h_old + h;
    let next = {
        x: current.x + turtle_walk_distance * Math.cos(h_new),
        y: current.y + turtle_walk_distance * Math.sin(h_new)
    }
    let wrapped_next = wrap(next.x, next.y);
    t.seth(h_new * 180 / Math.PI)
    if (wrapped_next.x !== next.x || wrapped_next.y !== next.y){
        t.jump(wrapped_next.x, wrapped_next.y);
    } else if (Math.random() < random_jump_chance) {
        t.penup();
        t.setx(-100 + Math.random() * 200);
        t.sety(-100 + Math.random() * 200);
        t.pendown();
    } else {
        t.forward(turtle_walk_distance);
    }
    return i < max_iterations;
}

function wrap(x, y, x_min = -100, x_max = 100, y_min = -100, y_max = 100){
    const x_range = x_max - x_min;
    const y_range = y_max - y_min;
    
    return {
        x: x < x_min ? x + x_range : x > x_max ? x - x_range : x,
        y: y < y_min ? y + y_range : y > y_max ? y - y_range : y
    }
}

//Generalized smoothstep from Wikipedia: (https://en.wikipedia.org/wiki/Smoothstep)

// Generalized smoothstep
function generalized_smooth_step(N, x) {
  x = clamp(x, 0, 1); // x must be equal to or between 0 and 1
  var result = 0;
  for (var n = 0; n <= N; ++n)
    result += pascalTriangle(-N - 1, n) *
              pascalTriangle(2 * N + 1, N - n) *
              Math.pow(x, N + n + 1);
  return result;
}

// Returns binomial coefficient without explicit use of factorials,
// which can't be used with negative integers
function pascalTriangle(a, b) {
  var result = 1; 
  for (var i = 0; i < b; ++i)
    result *= (a - i) / (i + 1);
  return result;
}

function clamp(x, lowerlimit, upperlimit) {
  if (x < lowerlimit)
    x = lowerlimit;
  if (x > upperlimit)
    x = upperlimit;
  return x;
}