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