Sphere plot 🌐

A bale demonstration 🐢🐢🐢🐢🐢🐢

Log in to post a comment.

const display = 0; //min=0 max=2 step=1 (Screen friendly, Plotter friendly - all, Plotter friendly - no obscured)
const mode = 3;//min=0 max=12 step=1 (1 Spiral, 2 Sprirals, 3 Spirals, 1 Double Spiral, 2 Double Sprirals, 3 Double Spirals, 1 Latitude, 2 Latitudes, 3 Latitudes, 1 Longitudes, 2 Longitudes, 3 Longitudes, Latitude & Longitude)
const spiralRotations = 10; //min=1 max=50 step=1
const latLongResolution = 18; //min=1 max=50 step=1
const shades = 10; //min=2 max=16 step=2 Fewer shades equals faster drawing
const perspective1000 = 3; //min=0 max=10 step=1
const yaw = 30; //min=0 max=360 step=1
const pitch = 60; //min=0 max=360 step=1
const roll = 60; //min=0 max=360 step=1

const radius = 80;

const perspective = perspective1000/1000;

// You can find the Turtle API reference here: https://turtletoy.net/syntax
Canvas.setpenopacity(display == 0? 1/shades: 1);

// Global code will be evaluated once.
turtlelib_init();
const bale = display == 0? new Bale(1/shades): new Turtle();
const max = spiralRotations * 4 * Math.PI;

const ptss = ((mode) => {
    const rots = [V.rot3d(0, 0, 0), V.rot3d(Math.PI/2, 0, 0), V.rot3d(0, 0, Math.PI/2)];
    switch(mode) {
        case 0:
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
            const spirals = mode % 3;
            const mirror = mode > 2? 1: 2;
            const mod = mirror == 1? 2: 1;
            const halfSpiralPts = Array.from({length: radius * max | 0}).map((e, i, a) => {
                return [
                    radius * Math.cos(max/mod        * i / a.length) * Math.sin(mirror*Math.PI * i/a.length),
                    radius * Math.cos(mirror*Math.PI * i / a.length),
                    radius * Math.sin(max/mod        * i / a.length) * Math.sin(mirror*Math.PI * i/a.length),
                ];
            });
            const rotHalfY = V.rot3d(0, Math.PI, 0);
            const spiralPts = (mode < 3)? halfSpiralPts: [...halfSpiralPts, ...halfSpiralPts.map(pt => V.trans(rotHalfY, pt)).reverse()];
            return rots.filter((e, i) => i <= spirals % 3).map(rot => spiralPts.map(pt => V.trans(rot, pt)));
        default:
            const circle = Array.from({length: radius * 2 * Math.PI | 0}).map((e, i, a) => {
                return [0, radius * Math.cos(2 * Math.PI * i / a.length), radius * Math.sin(2 * Math.PI * i / a.length)]
            });
            const longitudeLines = [];
            for(let i = 0; i < latLongResolution; i++) {
                const rot = V.rot3d(0, Math.PI * i / latLongResolution, 0, 0);
                longitudeLines.push(circle.map(pt => V.trans(rot, pt)));
            }
            const halfLatitudeLines = [];
            for(let i = latLongResolution; i > 0; i--) {
                const rho = (Math.PI/2) * i / latLongResolution;
                const r = radius * Math.sin(rho);
                halfLatitudeLines.push(Array.from({length: 2 * Math.PI * r | 0}).map((e, i, a) => {
                    return [
                        r * Math.cos(2 * Math.PI * i / a.length),
                        radius * Math.cos(rho),
                        r * Math.sin(2 * Math.PI * i / a.length)
                    ];
                }));
            }
            const latitudeLines = [...halfLatitudeLines.map(e => e).reverse(), ...halfLatitudeLines.filter((e,i) => i > 0).map(e => e.map(e => [e[0], -e[1], e[2]]))];
            
            switch(mode - 6) {
                case 0:
                case 1:
                case 2:
                    return rots.filter((e, i) => i <= mode - 6).flatMap(rot => latitudeLines.map(e => e.map(pt => V.trans(rot, pt))));
                case 3:
                case 4:
                case 5:
                    return rots.filter((e, i) => i <= mode - 9).flatMap(rot => longitudeLines.map(e => e.map(pt => V.trans(rot, pt))));
                default:            
                    return [...longitudeLines, ...latitudeLines];
            }
    }
})(mode);

const maxVisibleZ = findMaxVisibleZ(radius, perspective);

const rot = V.rot3d(Math.PI*yaw/180,Math.PI*pitch/180,Math.PI*roll/180);
// The walk function will be called until it returns false.
let drawing = false;
let key = 0;
let offset = 0;
function walk(i) {
    const pts = ptss[key];
    const index = i - offset;
    
    if(index == 0) draw('jump', pts[pts.length - 1]);
    draw('goto', pts[index]);
    
    if(index == pts.length - 1) {
        key++;
        offset += pts.length;
    }
    
    return key < ptss.length;
}

function draw(fn, pto) {
    const pt = V.trans(rot, pto);
    if(display == 2 && pt[2] < maxVisibleZ) {
        drawing = false;
        return;
    }
    if(display == 0) bale.setOpacity(.2 + .8 * (pt[2] / (2*radius) + .5));
    bale[drawing || display < 2? fn: 'jump'](project2d(pt));
    drawing = true;
}
    
function project2d(vec3) {
    return V.add([vec3[0], vec3[1]], V.scale([vec3[0], vec3[1]], vec3[2] * perspective));
}

////////////////////////////////////////////////////////////////
// Bale utility code 2.0 - Created by Jurgen Westerhof 2025
// https://turtletoy.net/turtle/7b8c486a30
// (Ab)using the opacity, usage:
//      Canvas.setpenopacity(sceneOpacity);
//      const bale = new Bale(sceneOpacity);
// Then use bale wherever you would use a turtle object to 'draw'
// in 'opacity' x (i.e Polygon hatching with a bale object and
// .25 interspacing), or:
//      bale.setOpacity(.5);
//      bale.jump(0,0);
//      bale.goto(40,0);
////////////////////////////////////////////////////////////////
function Bale(sceneOpacity, turtleClass = Turtle) {
    class Turtle {
        #useTurtleCount = 0;
        #turtles = [];
        #sceneOpacity = 0;
        constructor(sceneOpacity, turtleClass) {
            this.#sceneOpacity = sceneOpacity;
            this.#turtles = Array.from({length: Math.max(1, this.getTurtleCountForOpacity(1))}, (e,i) => new turtleClass());
            this.#useTurtleCount = this.#turtles.length;
        }
        setOpacity(opacity) {
            if(opacity < 0 || 1 < opacity) {
                throw new Error(`Out of bounds opacity. 0 <= opacity <= 1, got ${opacity}`);
            }
            this.#useTurtleCount = this.getTurtleCountForOpacity(opacity);
        }
        getProp(prop) {
            return this.#turtles[0][prop];
        }
        invokeProp(action, a, b, c, d, e, f, g) {
            return this.#turtles.reduce((acc, turtle, i) => turtle[action == 'goto' && this.#useTurtleCount <= i? 'jump': action](a, b, c, d, e, f, g), 0);
        }
        getTurtleCountForOpacity(targetOpacity) {
            if (targetOpacity <= 0) return 0;
            if (targetOpacity >= .5 && this.#turtles.length == 1) return 1;
            return Math.round(Math.log(1 - Math.min(.99, targetOpacity)) / Math.log(1 - this.#sceneOpacity));
        }
    }
    
    const TurtleProxy = {
        get(target, prop, receiver) {
            switch(prop) {
                case 'setOpacity':
                    return (opacity) => target.setOpacity(opacity);
                case 'clone':
                    throw new Error('Cloning not implemented');
                case 'position': //for faster processing, known functions
                case 'pos':      //of a turtle that don't change the
                case 'xcor':     //the state of a turtle but just tell
                case 'x':        //the state of a turtle, there's no need
                case 'ycor':     //to iterate over the whole bale but
                case 'y':        //only return the value from the first
                case 'heading':  //turtle in the bale. That's where these
                case 'h':        //9 cases are an exception to the rest
                case 'isdown':   //of a turtle's functions
                    return target.getProp(prop);
                default:
                    return (a, b, c, d, e, f, g) => target.invokeProp(prop, a, b, c, d, e, f, g);
            }
        },
    };
    return new Proxy(new Turtle(sceneOpacity, turtleClass), TurtleProxy);
}

function findMaxVisibleZ(r, p) {
    // Newton-Raphson to solve: -sin(θ)(1 + r * p * sin(θ)) + r * p * cos²(θ) = 0

    function f(theta) {
        const sin = Math.sin(theta);
        const cos = Math.cos(theta);
        return -sin * (1 + r * p * sin) + r * p * cos * cos;
    }

    function df(theta) {
        const sin = Math.sin(theta);
        const cos = Math.cos(theta);
        const d_sin = Math.cos(theta);
        const d_cos = -Math.sin(theta);
        // Derivative of the function above (computed by hand or symbolic tool)
        return -Math.cos(theta) * (1 + 2 * r * p * sin) - 2 * r * p * sin * cos;
    }

    // Initial guess (mid-front hemisphere)
    let theta = 0;
    let i = 0;

    while (i < 100) {
        const y = f(theta);
        const dy = df(theta);
        if (Math.abs(dy) < 1e-6) break; // avoid division by zero
        const nextTheta = theta - y / dy;
        if (Math.abs(nextTheta - theta) < 1e-8) break;
        theta = nextTheta;
        i++;
    }

    // Now get z from theta
    const z = r * Math.sin(theta);
    return z;
}


// Below is automatically maintained by Turtlelib 1.0
// Changes below this comment might interfere with its correct functioning.
function turtlelib_init() {
	turtlelib_ns_c6665b0e9b_Jurgen_Vector_Math();
}
// Turtlelib Jurgen Vector Math v 4 - start - {"id":"c6665b0e9b","package":"Jurgen","name":"Vector Math","version":"4"}
function turtlelib_ns_c6665b0e9b_Jurgen_Vector_Math() {
/////////////////////////////////////////////////////////
// Vector functions - Created by Jurgen Westerhof 2024 //
/////////////////////////////////////////////////////////
class Vector {
    static add  (a,b) { return a.map((v,i)=>v+b[i]); }
    static sub  (a,b) { return a.map((v,i)=>v-b[i]); }
    static mul  (a,b) { return a.map((v,i)=>v*b[i]); }
    static div  (a,b) { return a.map((v,i)=>v/b[i]); }
    static scale(a,s) { return a.map(v=>v*s); }

    static det(m)                { return m.length == 1? m[0][0]: m.length == 2 ? m[0][0]*m[1][1]-m[0][1]*m[1][0]: m[0].reduce((r,e,i) => r+(-1)**(i+2)*e*this.det(m.slice(1).map(c => c.filter((_,j) => i != j))),0); }
    static angle(a)              { return Math.PI - Math.atan2(a[1], -a[0]); } //compatible with turtletoy heading
    static rot2d(angle)          { return [[Math.cos(angle), -Math.sin(angle)], [Math.sin(angle), Math.cos(angle)]]; }
    static rot3d(yaw,pitch,roll) { return [[Math.cos(yaw)*Math.cos(pitch), Math.cos(yaw)*Math.sin(pitch)*Math.sin(roll)-Math.sin(yaw)*Math.cos(roll), Math.cos(yaw)*Math.sin(pitch)*Math.cos(roll)+Math.sin(yaw)*Math.sin(roll)],[Math.sin(yaw)*Math.cos(pitch), Math.sin(yaw)*Math.sin(pitch)*Math.sin(roll)+Math.cos(yaw)*Math.cos(roll), Math.sin(yaw)*Math.sin(pitch)*Math.cos(roll)-Math.cos(yaw)*Math.sin(roll)],[-Math.sin(pitch), Math.cos(pitch)*Math.sin(roll), Math.cos(pitch)*Math.cos(roll)]]; }
    static trans(matrix,a)       { return a.map((v,i) => a.reduce((acc, cur, ci) => acc + cur * matrix[ci][i], 0)); }
    //Mirror vector a in a ray through [0,0] with direction mirror
    static mirror2d(a,mirror)    { return [Math.atan2(...mirror)].map(angle => this.trans(this.rot2d(angle), this.mul([-1,1], this.trans(this.rot2d(-angle), a)))).pop(); }

    static equals(a,b)   { return !a.some((e, i) => e != b[i]); }
    static approx(a,b,p) { return this.len(this.sub(a,b)) < (p === undefined? .001: p); }
    static norm  (a)     { return this.scale(a,1/this.len(a)); }
    static len   (a)     { return Math.hypot(...a); }
    static lenSq (a)     { return a.reduce((a,c)=>a+c**2,0); }
    static lerp  (a,b,t) { return a.map((v, i) => v*(1-t) + b[i]*t); }
    static dist  (a,b)   { return Math.hypot(...this.sub(a,b)); }
    
    static dot  (a,b)   { return a.reduce((a,c,i) => a+c*b[i], 0); }
    static cross(...ab) { return ab[0].map((e, i) => ab.map(v => v.filter((ee, ii) => ii != i))).map((m,i) => (i%2==0?-1:1)*this.det(m)); }
    
    static clamp(a,min,max) { return a.map((e,i) => Math.min(Math.max(e, min[i]), max[i])) };
    static rotateClamp(a,min,max) { return a.map((e,i) => {const d = max[i]-min[i];if(d == 0) return min[i];while(e < min[i]) { e+=d; }while(e > max[i]) { e-=d; }return e;});
    }
}
this.V = Vector;
}
// Turtlelib Jurgen Vector Math v 4 - end