3D Contour Lines Island

Fork of reinder's Contour Lines Island. Projected into 3D.

Log in to post a comment.

// LL 2021

// Forked from "Contour Lines Island" by reinder
// Contour Lines Island. Created by Reinder Nijhoff 2020 - @reindernijhoff
// https://turtletoy.net/turtle/09e8dda306

// Added 3D projection and bells and whistles

const turtle = new Turtle();

const layers = 35; // min=1 max=100 step=1
const precision = 2; // min=1 max=5 step=1
const seed = 0; // min=0, max=100, step=1
const border = 0; // min=0 max=1 step=1 (Square,Round)
const exaggeration = 4; // min=0 max=5 step=0.1
const camera_angle = 0.14; // min=0 max=1 step=0.01
const camera_height = 250; /// min=0 max=500 step=50
const camera_distance = 300;
const style = 1; /// min=0 max=1 step=1 (Polygons (fast),Polygons (slow))

const noise = SimplexNoise(seed ? seed : Math.floor(Math.random() * 1000));
const heightFieldLookUp = p => heightField(p);

function walk(i, t) {
    if (i==0) {
        polygons = new Polygons();
        cameraPos = [camera_distance * Math.cos(Math.PI * 2 * (camera_angle + t)), camera_height, camera_distance * Math.sin(Math.PI * 2 * (camera_angle + t))];
        viewProjectionMatrix = setupCamera(cameraPos, cameraLookAt);
    }
    
    const z = (layers-1-i)*7/layers+0.1;
    const lines = ContourLines(z, precision, heightFieldLookUp);

    var paths = [];
    lines.forEach(line => {
        const point0 = project(line[0][0], line[0][1], z);
        const point1 = project(line[1][0], line[1][1], z);
        appendToPaths(paths, [point0, point1]);
    });

    paths = reducePaths(paths);

    paths.forEach(path => {
        var points = [];
        path.forEach(point => {
            points.push(point);
        });
        drawPoints(points);
    });

    return i < layers-1;
}

function appendToPaths(paths, line) {
    var found = false;
    for (var i=0; i<paths.length && !found; i++) {
        const start0 = paths[i][0];
        const end0 = paths[i][paths[i].length-1];
        const start1 = line[0];
        const end1 = line[line.length-1];
        
        if (found = comparePoints(end0, start1)) {
            for (var j=1; j<line.length; j++) { paths[i].push(line[j]); }
        } else if (found = comparePoints(end0, end1)) {
            for (var j=line.length-2; j>=0; j--) { paths[i].push(line[j]); }
        } else if (found = comparePoints(start0, start1)) {
            for (var j=1; j<line.length; j++) { paths[i].unshift(line[j]); }
        } else if (found = comparePoints(start0, end1)) {
            for (var j=line.length-2; j>=0; j--) { paths[i].unshift(line[j]); }
        }
    }
    if (!found) paths.push(line);
}

function comparePoints(point0, point1) {
    const distance = Math.hypot(point0[0] - point1[0], point0[1] - point1[1]);
    const epsilon = 0.001;
    return distance < epsilon;
}

function reducePaths(paths) {
    var loop = true;
    while (loop) {
        const new_paths = [];
        for (var i=0; i<paths.length; i++) appendToPaths(new_paths, paths[i]);
        loop = new_paths.length < paths.length;
        paths = new_paths;
    }
    return paths;
}

function project(x, y, z) {
    const p = transform4([x, z * exaggeration, y, 1], viewProjectionMatrix);
    const s = 50;
    return [p[0]/p[3]*s, -p[1]/p[3]*s];
}

function heightField(p) {
    let x = p[0]/75, y = p[1]/75;
    if (border==0) if (Math.max(Math.abs(x), Math.abs(y)) > 0.8) return 0;
    let height = noise.noise2D([x,y]);
    height += .50 * noise.noise2D([x*2+3.4,y*2-56.1]);
    height += .25 * Math.abs(1-noise.noise2D([x*4+21.2,y*4+.5]));
    height += .15 * Math.abs(1-noise.noise2D([x*8+421.12,y*8+21.3]));
    if (border==1) height += 1-2.5*((p[0]/100)**2 + (p[1]/100)**2);
    else height += 1;
    return Math.max(0,height) ** 2;
}

function drawPoints(points) {
    if (style == 0) {
        turtle.jump(points[points.length-1]);
        points.forEach(p=>turtle.goto(p));
    } else {
        const p1 = polygons.create();
        p1.addPoints(...points);
        p1.addOutline();
        polygons.draw(turtle, p1, true);
    }
}

////////////////////////////////////////////////////////////////
// Projection from reinder's https://turtletoy.net/turtle/b3acf08303
let cameraPos, viewProjectionMatrix;
const cameraLookAt = [0,0,0];
function setupCamera(t,e){const m=lookAt4m(t,e,[0,1,0]),n=perspective4m(.25,1);return multiply4m(n,m)}
function lookAt4m(o,n,r){const s=new Float32Array(16);n=normalize3(sub3(o,n)),r=normalize3(cross3(r,n));const t=normalize3(cross3(n,r));return s[0]=r[0],s[1]=t[0],s[2]=n[0],s[3]=0,s[4]=r[1],s[5]=t[1],s[6]=n[1],s[7]=0,s[8]=r[2],s[9]=t[2],s[10]=n[2],s[11]=0,s[12]=-(r[0]*o[0]+r[1]*o[1]+r[2]*o[2]),s[13]=-(t[0]*o[0]+t[1]*o[1]+t[2]*o[2]),s[14]=-(n[0]*o[0]+n[1]*o[1]+n[2]*o[2]),s[15]=1,s}
function perspective4m(t,n){const e=new Float32Array(16).fill(0,0);return e[5]=1/Math.tan(t/2),e[0]=e[5]/n,e[10]=e[11]=-1,e}
function multiply4m(t,r){const l=new Float32Array(16);for(let n=0;16>n;n+=4)for(let o=0;4>o;o++)l[n+o]=r[n+0]*t[0+o]+r[n+1]*t[4+o]+r[n+2]*t[8+o]+r[n+3]*t[12+o];return l}
function transform4(r,n){const t=new Float32Array(4);for(let o=0;4>o;o++)t[o]=n[o]*r[0]+n[o+4]*r[1]+n[o+8]*r[2]+n[o+12];return t}
function normalize3(a) { return scale3(a,1/len3(a)); }
function scale3(a,b) { return [a[0]*b,a[1]*b,a[2]*b]; }
function len3(a) { return Math.sqrt(dot3(a,a)); }
function sub3(a,b) { return [a[0]-b[0],a[1]-b[1],a[2]-b[2]]; }
function dot3(a,b) { return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; }
function cross3(a,b) { return [a[1]*b[2]-a[2]*b[1],a[2]*b[0]-a[0]*b[2],a[0]*b[1]-a[1]*b[0]]; }


////////////////////////////////////////////////////////////////
// Contour Lines utility code. Created by Reinder Nijhoff 2020
// https://turtletoy.net/turtle/104c4775c5
////////////////////////////////////////////////////////////////
function ContourLines(t,n,r){const o=(t,n,r)=>{if(n[2]===r[2])return!1;const o=(t-n[2])/(r[2]-n[2]);return!(o<=0||o>1)&&[n[0]+(r[0]-n[0])*o,n[1]+(r[1]-n[1])*o]},s=(t,n,r,s)=>{const u=[],e=o(t,n,r),c=o(t,r,s),f=o(t,s,n);return e&&c&&u.push([e,c]),e&&f&&u.push([e,f]),c&&f&&u.push([c,f]),u},u=[];for(let o=-100;o<=100;o+=n)for(let e=-100;e<=100;e+=n){const c=[[o,e],[o+n,e],[o+n,e+n],[o,e+n]];c.forEach(t=>t[2]=r(t));const f=[o+n/2,e+n/2,r([o+n/2,e+n/2])];for(let n=0;n<4;n++)u.push(...s(t,c[n],c[n+1&3],f))}return u}

////////////////////////////////////////////////////////////////
// Simplex Noise utility code. Created by Reinder Nijhoff 2020
// https://turtletoy.net/turtle/6e4e06d42e
// Based on: http://webstaff.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
////////////////////////////////////////////////////////////////
function SimplexNoise(t=1){const r=[[1,1,0],[-1,1,0],[1,-1,0],[-1,-1,0],[1,0,1],[-1,0,1],[1,0,-1],[-1,0,-1],[0,1,1],[0,-1,1],[0,1,-1],[0,-1,-1]],o=new Uint8Array(512),a=(Math.sqrt(3)-1)/2,n=(3-Math.sqrt(3))/6,s=(t,r)=>t[0]*r[0]+t[1]*r[1],h=(t,r)=>[t[0]-r[0],t[1]-r[1]];return new class{constructor(t=1){for(let t=0;t<512;t++)o[t]=255&t;for(let r=0;r<255;r++){const a=(t=this.hash(r+t))%(256-r)+r,n=o[r];o[r+256]=o[r]=o[a],o[a+256]=o[a]=n}}noise2D(t){const e=s(t,[a,a]),c=[Math.floor(t[0]+e),Math.floor(t[1]+e)],l=255&c[0],M=255&c[1],i=s(c,[n,n]),f=h(t,h(c,[i,i])),u=f[0]>f[1]?[1,0]:[0,1],m=h(h(f,u),[-n,-n]),x=h(f,[1-2*n,1-2*n]);let q=Math.max(0,.5-s(f,f))**4*s(r[o[l+o[M]]%12],f);return q+=Math.max(0,.5-s(m,m))**4*s(r[o[l+u[0]+o[M+u[1]]]%12],m),70*(q+=Math.max(0,.5-s(x,x))**4*s(r[o[l+1+o[M+1]]%12],x))}hash(t){const r=1103515245*((t=1103515245*(t>>1^t))^t>>3);return r^r>>16}}(t)}

////////////////////////////////////////////////////////////////
// Polygon Clipping utility code - Created by Reinder Nijhoff 2019
// https://turtletoy.net/turtle/a5befa1f8d
////////////////////////////////////////////////////////////////
function Polygons(){let t=[];const s=class{constructor(){this.cp=[],this.dp=[],this.aabb=[]}addPoints(...t){let s=1e5,e=-1e5,h=1e5,i=-1e5;(this.cp=[...this.cp,...t]).forEach(t=>{s=Math.min(s,t[0]),e=Math.max(e,t[0]),h=Math.min(h,t[1]),i=Math.max(i,t[1])}),this.aabb=[(s+e)/2,(h+i)/2,(e-s)/2,(i-h)/2]}addSegments(...t){t.forEach(t=>this.dp.push(t))}addOutline(){for(let t=0,s=this.cp.length;t<s;t++)this.dp.push(this.cp[t],this.cp[(t+1)%s])}draw(t){for(let s=0,e=this.dp.length;s<e;s+=2)t.jump(this.dp[s]),t.goto(this.dp[s+1])}addHatching(t,e){const h=new s;h.cp.push([-1e5,-1e5],[1e5,-1e5],[1e5,1e5],[-1e5,1e5]);const i=Math.sin(t)*e,n=Math.cos(t)*e,a=200*Math.sin(t),p=200*Math.cos(t);for(let t=.5;t<150/e;t++)h.dp.push([i*t+p,n*t-a],[i*t-p,n*t+a]),h.dp.push([-i*t+p,-n*t-a],[-i*t-p,-n*t+a]);h.boolean(this,!1),this.dp=[...this.dp,...h.dp]}inside(t){let s=0;for(let e=0,h=this.cp.length;e<h;e++)this.segment_intersect(t,[.13,-1e3],this.cp[e],this.cp[(e+1)%h])&&s++;return 1&s}boolean(t,s=!0){if(s&&Math.abs(this.aabb[0]-t.aabb[0])-(t.aabb[2]+this.aabb[2])>=0&&Math.abs(this.aabb[1]-t.aabb[1])-(t.aabb[3]+this.aabb[3])>=0)return this.dp.length>0;const e=[];for(let h=0,i=this.dp.length;h<i;h+=2){const i=this.dp[h],n=this.dp[h+1],a=[];for(let s=0,e=t.cp.length;s<e;s++){const h=this.segment_intersect(i,n,t.cp[s],t.cp[(s+1)%e]);!1!==h&&a.push(h)}if(0===a.length)s===!t.inside(i)&&e.push(i,n);else{a.push(i,n);const h=n[0]-i[0],p=n[1]-i[1];a.sort((t,s)=>(t[0]-i[0])*h+(t[1]-i[1])*p-(s[0]-i[0])*h-(s[1]-i[1])*p);for(let h=0;h<a.length-1;h++)(a[h][0]-a[h+1][0])**2+(a[h][1]-a[h+1][1])**2>=.001&&s===!t.inside([(a[h][0]+a[h+1][0])/2,(a[h][1]+a[h+1][1])/2])&&e.push(a[h],a[h+1])}}return(this.dp=e).length>0}segment_intersect(t,s,e,h){const i=(h[1]-e[1])*(s[0]-t[0])-(h[0]-e[0])*(s[1]-t[1]);if(0===i)return!1;const n=((h[0]-e[0])*(t[1]-e[1])-(h[1]-e[1])*(t[0]-e[0]))/i,a=((s[0]-t[0])*(t[1]-e[1])-(s[1]-t[1])*(t[0]-e[0]))/i;return n>=0&&n<=1&&a>=0&&a<=1&&[t[0]+n*(s[0]-t[0]),t[1]+n*(s[1]-t[1])]}};return{list:()=>t,create:()=>new s,draw:(s,e,h=!0)=>{for(let s=0;s<t.length&&e.boolean(t[s]);s++);e.draw(s),h&&t.push(e)}}}