Quick experiment with marching cubes using the mountains from Contour Lines Island
Log in to post a comment.
// LL 2021 const turtle = new Turtle(); const detail = 0.0; /// min=0 max=1 step=0.1 const perspective = 2; /// min=1 max=3 step=0.1 const camera_theta = 0.3; // min=-1 max=1 step=0.01 const camera_phi = 0.3; // min=-0.99 max=1 step=0.01 const camera_r = 28; // min=0.1 max=40 step=0.1 const look_at_z = 9; // min=-100 max=100 step=0.1 const inside_lines = 0.45; // min=0 max=1 step=0.01 const style = 2; // min=0 max=3 step=1 (Preview,All outlines,Silhouette,Hatched) const render = 1; // min=0 max=1 step=1 (Voxels,Marching cubes) const seed = 0; // min=0 max=100 step=1 const detail_max_length = Math.max(0.9, 0.9 + 5 * (1 - detail)); var silhouette = new Silhouette(); const noise = SimplexNoise(seed ? seed : Math.floor(Math.random() * 1000)); function walk(i, t) { if (i==0) { if (t == 0 || t == 1) initOnce(); initFrame(t); } if (silhouette.faceCount() < 1) { // for (var y = -100; y <= 100; y += 1) { // const p1 = polygons.create(); // const dpoints = []; // const sx = Math.cos(y/55) * 70 + random() * 50; // for (var x=-sx; x<=sx; x += 1) { // const wy = Math.abs(Math.sin(y/4+x/2)) * 2 + y; // dpoints.push([x, wy]); // dpoints.unshift([x, wy]); // } // p1.addPoints(...dpoints); // p1.addOutline(); // polygons.draw(turtle, p1, false); // } return false; } silhouette.nextFace().draw(); return true; } function initFrame(t) { const cameraOffset = [ camera_r * perspective ** 3 * Math.cos((camera_theta+t*2) * Math.PI) * Math.sin((camera_phi/2+0.5) * Math.PI), camera_r * perspective ** 3 * Math.sin((camera_theta+t*2) * Math.PI) * Math.sin((camera_phi/2+0.5) * Math.PI), -camera_r * perspective ** 3 * Math.cos((camera_phi/2+0.5) * Math.PI) ]; const cameraLookAt = [0, 0, look_at_z]; viewProjectionMatrix = setupCamera(add3(cameraOffset, cameraLookAt), cameraLookAt); polygons = new Polygons(); silhouette.processFrameModels(); silhouette.sortFaces(); } function initOnce(t) { seed_t = (t < 1 && seed == 0) ? (Math.random() * 100 | 0) : seed; rng = undefined; initScene(); silhouette.processOnceModels(); } function initScene() { const count = 128; const mdl = makeMountains(count); var matrix = new Matrix(); matrix.translate(0, count/2, 0); matrix.scale(128/count); mdl.transform(matrix); silhouette.addModel(mdl); } ///////////////////////////////////////////////////////////////////// // Prototype Silhouette utility code - Created by Lionel Lemarie 2021 // https://turtletoy.net/turtle/334500a2c5 function Silhouette() { const models = []; const all_faces = []; class Model { constructor() { this.faces = []; this.edges = []; this.hide_overlap = true; } transform(matrix) { this.faces.forEach(face => { face.transform(matrix); }); } processOnce() { if (detail && style) this.subdivideDetail(detail_max_length); this.updateEdgeList(); } processFrame() { this.faces.forEach(face => { face.projectAndAdd(); }); this.findStaticOutlines(); this.findProjectedOutlines(); this.updateOutlineMasks(); } updateOutlineMasks() { this.edges.forEach(edge => { edge.faces.forEach(face => { const pc = face.points.length; if (pc > 1) { for (var i=0; i<pc; i++) { const hash0 = getPointHash(face.points[i]); const hash1 = getPointHash(face.points[(i+1)%pc]); if (edge.hash == hash0 + hash1 || edge.hash == hash1 + hash0) { if (edge.state) { face.outline_mask |= 1 << i; } else { face.outline_mask &= ~(1 << i); } } } } }); }); } addFace(points) { const face = new Face(points); this.faces.push(face); } merge(model) { model.faces.forEach(face => { this.faces.push(face); }) } subdivideDetail(max_length) { var nfaces = this.faces.length; var loop = true; while (loop) { const old_faces = this.faces; this.faces = []; old_faces.forEach(face => { const new_faces = face.getSubdivided(max_length); this.faces = [...this.faces, ...new_faces]; }); loop = this.faces.length != nfaces; nfaces = this.faces.length; } } subdivideCount(count) { while (count-- > 0) { const old_faces = this.faces; this.faces = []; old_faces.forEach(face => { const new_faces = face.getSubdivided(0); this.faces = [...this.faces, ...new_faces]; }); } } updateEdgeList() { this.edges = []; const edge_lookup = {}; this.faces.forEach(face => { const pc = face.points.length; if (pc > 1) { for (var i=0; i<pc; i++) { const hash0 = getPointHash(face.points[i]); const hash1 = getPointHash(face.points[(i+1)%pc]); if ((hash0 + hash1) in edge_lookup) { const edge_id = edge_lookup[hash0 + hash1]; this.addFaceToEdge(edge_id, face); } else if ((hash1 + hash0) in edge_lookup) { const edge_id = edge_lookup[hash1 + hash0]; this.addFaceToEdge(edge_id, face); } else { const edge_id = this.edges.length; edge_lookup[hash0 + hash1] = edge_id; this.edges.push({ faces: [face], hash: hash0 + hash1, state: 1 }); } } } }); } addFaceToEdge(edge_id, new_face) { var good = true; this.edges[edge_id].faces.forEach(face => { if (face.matches(new_face)) { good = false; if (this.hide_overlap) face.overlap = true; new_face.overlap = true; } }); if (good) this.edges[edge_id].faces.push(new_face); } findStaticOutlines() { this.edges.forEach(edge => { edge.state = 1; for (var i=0, fl=edge.faces.length; i<fl && edge.state; i++) { if (edge.faces[i].overlap) continue; const nfi = edge.faces[i].getStaticNormal(); for (var j=i+1; j<fl && edge.state; j++) { if (edge.faces[j].overlap) continue; const nfj = edge.faces[j].getStaticNormal(); const d = len3(sub3(nfi, nfj)); if (d < inside_lines * 2) { edge.state = 0; } } } }); } findProjectedOutlines() { this.edges.forEach(edge => { var found_pos = false, found_neg = false; edge.faces.forEach(face => { if (!face.overlap) { const nf = face.getProjectedNormal(); const EPS = 0.001; if (nf[2] < EPS) found_neg = true; if (nf[2] >= -EPS) found_pos = true; } }); if (found_pos && found_neg) edge.state = 1; }); } } function getPointHash(point) { const mult = 100; const x0 = Math.round(point[0] * mult), y0 = Math.round(point[1] * mult), z0 = Math.round(point[2] * mult); return `${x0},${y0},${z0}`; } class Face { constructor(points) { this.points = [...points]; this.z = 0; this.projected_points = []; this.outline_mask = -1; this.overlap = false; } draw() { if (this.projected_points.length < 2 || this.overlap) return; var good = true; this.projected_points.forEach(p => good &= Math.min(Math.abs(p[0]), Math.abs(p[1])) < 100 ); if (!good) return; if (style == 0) { // turtle.jump(this.projected_points[this.projected_points.length-1]); // this.projected_points.forEach(p=>turtle.goto(p)); for (var i=0; i<this.projected_points.length; i++) { if (this.outline_mask & (1 << i)) { turtle.jump(this.projected_points[i]); turtle.goto(this.projected_points[(i+1) % this.projected_points.length]); } } } else { const p1 = polygons.create(); p1.addPoints(...this.projected_points); if (style == 1) { p1.addOutline(); } else if (style > 1) { for (var i=0; i<this.projected_points.length; i++) { if (this.outline_mask & (1 << i)) { p1.addSegments(p1.cp[i], p1.cp[(i+1) % this.projected_points.length]); } } if (style > 2) { const hmin = 0.15, hmax = 0.9; const hatching = hmin + (hmax - hmin) * this.getLight(); p1.addHatching(-Math.PI/4, hatching); } } polygons.draw(turtle, p1, true); } } getStaticNormal() { if (this.cached_static_normal === undefined) { if (this.points.length < 3) this.cached_static_normal = [0, 1, 0]; else this.cached_static_normal = normalize3(cross3(sub3(this.points[1], this.points[0]), sub3(this.points[2], this.points[0]))); } return this.cached_static_normal; } getProjectedNormal() { if (this.projected_points.length < 3) return [0, 1, 0]; return normalize3(cross3(sub3(this.projected_points[1], this.projected_points[0]), sub3(this.projected_points[2], this.projected_points[0]))); } getLight() { const n = this.getProjectedNormal(); return n[0] * 0.5 + 0.5; } transform(matrix) { for (var i=0, c=this.points.length; i<c; i++) { this.points[i] = matrix.transform(this.points[i]); } } projectAndAdd() { if (this.overlap) return; this.projected_points = []; this.z = 0; this.points.forEach(point => { const pp = project(point); if (pp === undefined) return; this.projected_points.push(pp); this.z += pp[2]; }) if (this.projected_points.length > 0) this.z /= this.projected_points.length; silhouette.addFace(this); } getSubdivided(max_length) { var long_index = -1; const pc = this.points.length; for (var i=0; i<pc; i++) { const len = len3(sub3(this.points[(i+1)%pc],this.points[i])); if (len > max_length) { max_length = len; long_index = i; } } if (long_index >= 0) { if (pc == 4) { const new_faces = []; new_faces.push(new Face([this.points[0], this.points[1], this.points[2]])); new_faces.push(new Face([this.points[2], this.points[3], this.points[0]])); return new_faces; } else { const new_faces = []; const point = mulf(add3(this.points[(long_index+1)%pc],this.points[long_index]), 0.5); for (var i=0; i<pc; i++) { if (i != long_index) { new_faces.push(new Face([this.points[i], this.points[(i+1)%pc], point])); } } return new_faces; } } return [ this ]; } matches(face) { const fl = face.points.length; if (fl != this.points.length) return false; if (fl < 1) return true; var first_id = undefined; { const hash0 = getPointHash(this.points[0]); for (var i=0; i<fl; i++) { const hash1 = getPointHash(face.points[i]); if (hash0 == hash1) { first_id = i; break; } } } if (first_id === undefined) return false; for (var i=1; i<fl; i++) { const j = (first_id - i + fl) % fl; const hash0 = getPointHash(this.points[i]); const hash1 = getPointHash(face.points[j]); if (hash0 != hash1) { for (var i2=1; i2<fl; i2++) { const j2 = (first_id + i2) % fl; const hash0 = getPointHash(this.points[i2]); const hash1 = getPointHash(face.points[j2]); if (hash0 != hash1) return false; } return true; } } return true; } } return { addModel: (model) => { models.push(model); }, newModel: () => { return new Model(); }, processFrameModels: () => { models.forEach(model => { model.processFrame(); }); }, processOnceModels: () => { models.forEach(model => { model.processOnce(); }); }, modelCount: () => { return models.length; }, sortFaces: () => { all_faces.sort(function(a, b) { return a.z - b.z; }); }, nextFace: () => { return all_faces.shift(); }, faceCount: () => { return all_faces.length; }, addFace: (face) => { all_faces.push(face); }, }; } class Matrix { constructor() { this.identity(); } identity() { this.matrix = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]; return this; } translate(tx, ty, tz) { const m2 = new Matrix(); m2.matrix[12] = tx; m2.matrix[13] = ty; m2.matrix[14] = tz; return this.multiply(m2); } scale(sx, sy, sz) { if (sy === undefined) sy = sx; if (sz === undefined) sz = sy; const m2 = new Matrix(); m2.matrix[0] *= sx; m2.matrix[5] *= sy; m2.matrix[10] *= sz; return this.multiply(m2); } rotateX(a) { const m2 = new Matrix(); m2.matrix[5] = Math.cos(a); m2.matrix[9] = Math.sin(a); m2.matrix[6] = -Math.sin(a); m2.matrix[10] = Math.cos(a); return this.multiply(m2); } rotateY(a) { const m2 = new Matrix(); m2.matrix[0] = Math.cos(a); m2.matrix[8] = -Math.sin(a); m2.matrix[2] = Math.sin(a); m2.matrix[10] = Math.cos(a); return this.multiply(m2); } rotateZ(a) { const m2 = new Matrix(); m2.matrix[0] = Math.cos(a); m2.matrix[1] = -Math.sin(a); m2.matrix[4] = Math.sin(a); m2.matrix[5] = Math.cos(a); return this.multiply(m2); } multiply(rhs) { const m1 = [...this.matrix]; const m2 = [...rhs.matrix]; this.matrix = []; for(let n=0; 16>n; n+=4) { for(let o=0; 4>o; o++) { this.matrix[n+o] = m1[n+0] * m2[0+o] + m1[n+1] * m2[4+o] + m1[n+2] * m2[8+o] + m1[n+3] * m2[12+o]; } } return this; } transform(point) { const p = [...point]; for (let i=0; i<3; i++) { p[i] = this.matrix[i] * point[0] + this.matrix[i+4] * point[1] + this.matrix[i+8] * point[2] + this.matrix[i+12]; } return p; } } const cube_points = []; for (var z=-1; z<=1; z+=2) { for (var y=-1; y<=1; y+=2) { for (var x=-1; x<=1; x+=2) { cube_points.push([x, y, z]); } } } const cube_faces = [ [0, 1, 2, 3], [7, 3, 5, 1], [6, 2, 7, 3], [0, 4, 1, 5], [4, 0, 6, 2], [6, 7, 4, 5] ]; function makeBox(w, h, d) { const model = silhouette.newModel(); cube_faces.forEach(f => { const quad = [ [...cube_points[f[0]]], [...cube_points[f[1]]], [...cube_points[f[3]]], [...cube_points[f[2]]] ]; quad.forEach(point => { point[0] *= w/2; point[1] *= h/2; point[2] *= d/2; }); model.addFace(quad); }); return model; } function heightField(p) { let x = p[0]/60, y = p[1]/60; let height = noise.noise2D([x,y]); height += .55 * noise.noise2D([x*2+3.4,y*2-56.1]); height += .35 * Math.abs(1-noise.noise2D([x*4+21.2,y*4+.5])); height += .25 * Math.abs(1-noise.noise2D([x*8+421.12,y*8+21.3])); // height += .15 * Math.abs(1-noise.noise2D([x*32+150.34,y*32+150.34])); height += 1 - 2.5 * ((p[0]/100)**2 + (p[1]/100)**2); height = Math.max(height, 0) ** 2; return height; } function makeMountains(count) { const grid= {}; const size = 180; const step = size / (count-1); for (var z = 1; z < count-1; z++) { const zz = -size/2 + size / count * z; for (var x = 1; x < count-1; x++) { const xx = -size/2 + size / count * x; const h = Math.max(1, Math.min(count-1, heightField([xx, zz]) * count / 32)); for (var y=1; y<h; y++) { for (var dz=-1; dz<=0; dz++) for (var dy=-1; dy<=0; dy++) for (var dx=-1; dx<=0; dx++) { const key = (x+dx) + (y+dy) * count + (z+dz) * count * count; if (!(key in grid)) grid[key] = 0; grid[key] |= !(dx || dy || dz); } } } } const model = modelFromGrid(grid, count); return model; } function modelFromGrid(grid, grid_size) { const model = silhouette.newModel(); Object.keys(grid).forEach(key => { const x = key % grid_size; const y = (key / grid_size | 0) % grid_size; const z = key / grid_size / grid_size| 0; const cx = (x - grid_size / 2); const cy = (y - grid_size / 2); const cz = (z - grid_size / 2); if (render) { const polys = getTriangles(grid, grid_size, x, y, z, [cx, cy, cz], 1/2); polys.forEach(poly => { model.addFace(poly); }); } else { if (grid[key]) { const mdl = makeBox(1, 1, 1); mdl.transform(new Matrix().translate(cx, cy, cz)); model.merge(mdl); } } }); return model; } // http://paulbourke.net/geometry/polygonise/ const tri_table = [ [], [0, 8, 3], [0, 1, 9], [1, 8, 3, 9, 8, 1], [1, 2, 10], [0, 8, 3, 1, 2, 10], [9, 2, 10, 0, 2, 9], [2, 8, 3, 2, 10, 8, 10, 9, 8], [3, 11, 2], [0, 11, 2, 8, 11, 0], [1, 9, 0, 2, 3, 11], [1, 11, 2, 1, 9, 11, 9, 8, 11], [3, 10, 1, 11, 10, 3], [0, 10, 1, 0, 8, 10, 8, 11, 10], [3, 9, 0, 3, 11, 9, 11, 10, 9], [9, 8, 10, 10, 8, 11], [4, 7, 8], [4, 3, 0, 7, 3, 4], [0, 1, 9, 8, 4, 7], [4, 1, 9, 4, 7, 1, 7, 3, 1], [1, 2, 10, 8, 4, 7], [3, 4, 7, 3, 0, 4, 1, 2, 10], [9, 2, 10, 9, 0, 2, 8, 4, 7], [2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4], [8, 4, 7, 3, 11, 2], [11, 4, 7, 11, 2, 4, 2, 0, 4], [9, 0, 1, 8, 4, 7, 2, 3, 11], [4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1], [3, 10, 1, 3, 11, 10, 7, 8, 4], [1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4], [4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3], [4, 7, 11, 4, 11, 9, 9, 11, 10], [9, 5, 4], [9, 5, 4, 0, 8, 3], [0, 5, 4, 1, 5, 0], [8, 5, 4, 8, 3, 5, 3, 1, 5], [1, 2, 10, 9, 5, 4], [3, 0, 8, 1, 2, 10, 4, 9, 5], [5, 2, 10, 5, 4, 2, 4, 0, 2], [2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8], [9, 5, 4, 2, 3, 11], [0, 11, 2, 0, 8, 11, 4, 9, 5], [0, 5, 4, 0, 1, 5, 2, 3, 11], [2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5], [10, 3, 11, 10, 1, 3, 9, 5, 4], [4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10], [5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3], [5, 4, 8, 5, 8, 10, 10, 8, 11], [9, 7, 8, 5, 7, 9], [9, 3, 0, 9, 5, 3, 5, 7, 3], [0, 7, 8, 0, 1, 7, 1, 5, 7], [1, 5, 3, 3, 5, 7], [9, 7, 8, 9, 5, 7, 10, 1, 2], [10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3], [8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2], [2, 10, 5, 2, 5, 3, 3, 5, 7], [7, 9, 5, 7, 8, 9, 3, 11, 2], [9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11], [2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7], [11, 2, 1, 11, 1, 7, 7, 1, 5], [9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11], [5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0], [11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0], [11, 10, 5, 7, 11, 5], [10, 6, 5], [0, 8, 3, 5, 10, 6], [9, 0, 1, 5, 10, 6], [1, 8, 3, 1, 9, 8, 5, 10, 6], [1, 6, 5, 2, 6, 1], [1, 6, 5, 1, 2, 6, 3, 0, 8], [9, 6, 5, 9, 0, 6, 0, 2, 6], [5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8], [2, 3, 11, 10, 6, 5], [11, 0, 8, 11, 2, 0, 10, 6, 5], [0, 1, 9, 2, 3, 11, 5, 10, 6], [5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11], [6, 3, 11, 6, 5, 3, 5, 1, 3], [0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6], [3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9], [6, 5, 9, 6, 9, 11, 11, 9, 8], [5, 10, 6, 4, 7, 8], [4, 3, 0, 4, 7, 3, 6, 5, 10], [1, 9, 0, 5, 10, 6, 8, 4, 7], [10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4], [6, 1, 2, 6, 5, 1, 4, 7, 8], [1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7], [8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6], [7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9], [3, 11, 2, 7, 8, 4, 10, 6, 5], [5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11], [0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6], [9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6], [8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6], [5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11], [0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7], [6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9], [10, 4, 9, 6, 4, 10], [4, 10, 6, 4, 9, 10, 0, 8, 3], [10, 0, 1, 10, 6, 0, 6, 4, 0], [8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10], [1, 4, 9, 1, 2, 4, 2, 6, 4], [3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4], [0, 2, 4, 4, 2, 6], [8, 3, 2, 8, 2, 4, 4, 2, 6], [10, 4, 9, 10, 6, 4, 11, 2, 3], [0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6], [3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10], [6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1], [9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3], [8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1], [3, 11, 6, 3, 6, 0, 0, 6, 4], [6, 4, 8, 11, 6, 8], [7, 10, 6, 7, 8, 10, 8, 9, 10], [0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10], [10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0], [10, 6, 7, 10, 7, 1, 1, 7, 3], [1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7], [2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9], [7, 8, 0, 7, 0, 6, 6, 0, 2], [7, 3, 2, 6, 7, 2], [2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7], [2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7], [1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11], [11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1], [8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6], [0, 9, 1, 11, 6, 7], [7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0], [7, 11, 6], [7, 6, 11], [3, 0, 8, 11, 7, 6], [0, 1, 9, 11, 7, 6], [8, 1, 9, 8, 3, 1, 11, 7, 6], [10, 1, 2, 6, 11, 7], [1, 2, 10, 3, 0, 8, 6, 11, 7], [2, 9, 0, 2, 10, 9, 6, 11, 7], [6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8], [7, 2, 3, 6, 2, 7], [7, 0, 8, 7, 6, 0, 6, 2, 0], [2, 7, 6, 2, 3, 7, 0, 1, 9], [1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6], [10, 7, 6, 10, 1, 7, 1, 3, 7], [10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8], [0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7], [7, 6, 10, 7, 10, 8, 8, 10, 9], [6, 8, 4, 11, 8, 6], [3, 6, 11, 3, 0, 6, 0, 4, 6], [8, 6, 11, 8, 4, 6, 9, 0, 1], [9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6], [6, 8, 4, 6, 11, 8, 2, 10, 1], [1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6], [4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9], [10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3], [8, 2, 3, 8, 4, 2, 4, 6, 2], [0, 4, 2, 4, 6, 2], [1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8], [1, 9, 4, 1, 4, 2, 2, 4, 6], [8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1], [10, 1, 0, 10, 0, 6, 6, 0, 4], [4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3], [10, 9, 4, 6, 10, 4], [4, 9, 5, 7, 6, 11], [0, 8, 3, 4, 9, 5, 11, 7, 6], [5, 0, 1, 5, 4, 0, 7, 6, 11], [11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5], [9, 5, 4, 10, 1, 2, 7, 6, 11], [6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5], [7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2], [3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6], [7, 2, 3, 7, 6, 2, 5, 4, 9], [9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7], [3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0], [6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8], [9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7], [1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4], [4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10], [7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10], [6, 9, 5, 6, 11, 9, 11, 8, 9], [3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5], [0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11], [6, 11, 3, 6, 3, 5, 5, 3, 1], [1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6], [0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10], [11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5], [6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3], [5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2], [9, 5, 6, 9, 6, 0, 0, 6, 2], [1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8], [1, 5, 6, 2, 1, 6], [1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6], [10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0], [0, 3, 8, 5, 6, 10], [10, 5, 6], [11, 5, 10, 7, 5, 11], [11, 5, 10, 11, 7, 5, 8, 3, 0], [5, 11, 7, 5, 10, 11, 1, 9, 0], [10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1], [11, 1, 2, 11, 7, 1, 7, 5, 1], [0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11], [9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7], [7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2], [2, 5, 10, 2, 3, 5, 3, 7, 5], [8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5], [9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2], [9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2], [1, 3, 5, 3, 7, 5], [0, 8, 7, 0, 7, 1, 1, 7, 5], [9, 0, 3, 9, 3, 5, 5, 3, 7], [9, 8, 7, 5, 9, 7], [5, 8, 4, 5, 10, 8, 10, 11, 8], [5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0], [0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5], [10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4], [2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8], [0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11], [0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5], [9, 4, 5, 2, 11, 3], [2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4], [5, 10, 2, 5, 2, 4, 4, 2, 0], [3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9], [5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2], [8, 4, 5, 8, 5, 3, 3, 5, 1], [0, 4, 5, 1, 0, 5], [8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5], [9, 4, 5], [4, 11, 7, 4, 9, 11, 9, 10, 11], [0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11], [1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11], [3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4], [4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2], [9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3], [11, 7, 4, 11, 4, 2, 2, 4, 0], [11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4], [2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9], [9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7], [3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10], [1, 10, 2, 8, 7, 4], [4, 9, 1, 4, 1, 7, 7, 1, 3], [4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1], [4, 0, 3, 7, 4, 3], [4, 8, 7], [9, 10, 8, 10, 11, 8], [3, 0, 9, 3, 9, 11, 11, 9, 10], [0, 1, 10, 0, 10, 8, 8, 10, 11], [3, 1, 10, 11, 3, 10], [1, 2, 11, 1, 11, 9, 9, 11, 8], [3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9], [0, 2, 11, 8, 0, 11], [3, 2, 11], [2, 3, 8, 2, 8, 10, 10, 8, 9], [9, 10, 2, 0, 9, 2], [2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8], [1, 10, 2], [1, 3, 8, 9, 1, 8], [0, 9, 1], [0, 3, 8], [] ]; const edges = [ [0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6], [6, 7], [7, 4], [4, 0], [5, 1], [6, 2], [7, 3] ]; const v_xyz = [ [-1, -1, -1], [1, -1, -1], [1, -1, 1], [-1, -1, 1], [-1, 1, -1], [1, 1, -1], [1, 1, 1], [-1, 1, 1] ]; function getTriangles(voxel_grid, grid_size, x, y, z, cxyz, r) { var cube_index = 0; for (var i=0; i<8; i++) { const dx = v_xyz[i][0] * .5 + .5, dy = v_xyz[i][1] * .5 + .5, dz = v_xyz[i][2] * .5 + .5; const idx = (x+dx) + (y+dy) * grid_size + (z+dz) * grid_size * grid_size; if (idx in voxel_grid && voxel_grid[idx] == 1) { cube_index |= 1 << i; } } const polys = []; var points = []; tri_table[cube_index].forEach( (t, i) => { const p0 = add3(mulf(v_xyz[edges[t][0]], r), cxyz); const p1 = add3(mulf(v_xyz[edges[t][1]], r), cxyz); const point = add3(p0, mulf(sub3(p1, p0), .5)); points.push(point); if (((i+1)%3) == 0) { polys.push(points); points = []; } }); return polys; } function makeTower(ox, oy, oz, height, sides, radius, div) { const astep = Math.PI * 2 / sides; for (var i=0; i<sides * height; i++) { const angle = i * astep; const y = (i / sides | 0); var matrix = new Matrix(); matrix.rotateY(Math.PI / 4); matrix.translate(radius, y, radius); matrix.rotateY(-angle + astep/2 * (y&1)); matrix.translate(ox, oy, oz); const box = makeBox(1, 1, radius * div); box.transform(matrix); silhouette.addModel(box); } } function makeWall(ox, oy, oz, rotY, width, height, brick_width, brick_height) { for (var x=-width/2; x<width/2; x+=brick_width) { for (var y=0; y<height; y+=brick_height) { var matrix = new Matrix(); matrix.translate(ox, oy, oz); matrix.translate(x + brick_width / 2 * ((y/brick_height|0)&1), y, 0); matrix.rotateY(rotY); const box = makeBox(brick_width, brick_height, 1); box.transform(matrix); silhouette.addModel(box); } } } function makeTorus(lr, lseg, cr, cseg, twist) { const torus = silhouette.newModel(); const lstep = Math.PI * 2 / lseg; const cstep = Math.PI * 2 / cseg; for (var i=0; i<lseg; i++) { const points = []; for (var k=0; k<2; k++) { points.push([]); const a = (i + k) * lstep; const cx = lr * Math.cos(a), cy = 0, cz = lr * Math.sin(a); const matrix = new Matrix().rotateY(a).translate(cx, cy, cz); for (var j=0; j<cseg; j++) { const b = j * cstep + Math.PI / 4 + a * twist; const point = matrix.transform([cr * Math.cos(b), cr * Math.sin(b), 0]); points[k].push(point); } } for (j=0; j<cseg; j++) { torus.addFace([ points[0][j], points[0][(j+1)%cseg], points[1][(j+1)%cseg], points[1][j] ]); } } return torus; } function makePQTorus(p, q, lr, lseg, cr, cseg, twist, star) { const torus = silhouette.newModel(); const lstep = Math.PI * 2 / lseg; const cstep = Math.PI * 2 / cseg; for (var i=0; i<lseg; i++) { const points = []; for (var k=0; k<2; k++) { const a = (i + k) * lstep; const r = lr * 0.5 * (2 + Math.sin(a * q)); const cx = r * Math.cos(a * p); const cy = r * 0.5 * Math.cos(a * q); const cz = r * Math.sin(a * p); const matrix = new Matrix(); matrix.rotateY(a * p); matrix.translate(cx, cy, cz); points.push([]); for (var j=0; j<cseg; j++) { const b = j * cstep + Math.PI / 4 + a * twist; const factor = 0.3 + (1 + Math.cos(a * p * 2)) * 0.5 * 0.7; const r2 = cr * ((j&1) ? 1 : star) * factor; const point = [ r2 * Math.cos(b), r2 * Math.sin(b), 0 ]; const tpoint = matrix.transform(point) points[k].push(tpoint); } } for (j=0; j<cseg; j++) { const point00 = points[0][j]; const point01 = points[0][(j+1)%cseg]; const point10 = points[1][j]; const point11 = points[1][(j+1)%cseg]; const quad = [ point00, point01, point11, point10 ]; torus.addFace(quad); } } return torus; } function project(op) { const p = transform4([op[0], op[2], op[1], 1], viewProjectionMatrix); const s = 5 * perspective **3; if (p[2] < 0) return undefined; return [ p[0]/p[3]*s, -p[1]/p[3]*s, p[2] ]; } //////////////////////////////////////////////////////////////// // Polygon Clipping utility code - Created by Reinder Nijhoff 2019 // (Polygon binning by Lionel Lemarie 2021) // https://turtletoy.net/turtle/a5befa1f8d //////////////////////////////////////////////////////////////// function Polygons(){const t=[],s=25,e=Array.from({length:s**2},t=>[]),n=class{constructor(){this.cp=[],this.dp=[],this.aabb=[]}addPoints(...t){let s=1e5,e=-1e5,n=1e5,h=-1e5;(this.cp=[...this.cp,...t]).forEach(t=>{s=Math.min(s,t[0]),e=Math.max(e,t[0]),n=Math.min(n,t[1]),h=Math.max(h,t[1])}),this.aabb=[s,n,e,h]}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,s){const e=new n;e.cp.push([-1e5,-1e5],[1e5,-1e5],[1e5,1e5],[-1e5,1e5]);const h=Math.sin(t)*s,o=Math.cos(t)*s,a=200*Math.sin(t),i=200*Math.cos(t);for(let t=.5;t<150/s;t++)e.dp.push([h*t+i,o*t-a],[h*t-i,o*t+a]),e.dp.push([-h*t+i,-o*t-a],[-h*t-i,-o*t+a]);e.boolean(this,!1),this.dp=[...this.dp,...e.dp]}inside(t){let s=0;for(let e=0,n=this.cp.length;e<n;e++)this.segment_intersect(t,[.1,-1e3],this.cp[e],this.cp[(e+1)%n])&&s++;return 1&s}boolean(t,s=!0){const e=[];for(let n=0,h=this.dp.length;n<h;n+=2){const h=this.dp[n],o=this.dp[n+1],a=[];for(let s=0,e=t.cp.length;s<e;s++){const n=this.segment_intersect(h,o,t.cp[s],t.cp[(s+1)%e]);!1!==n&&a.push(n)}if(0===a.length)s===!t.inside(h)&&e.push(h,o);else{a.push(h,o);const n=o[0]-h[0],i=o[1]-h[1];a.sort((t,s)=>(t[0]-h[0])*n+(t[1]-h[1])*i-(s[0]-h[0])*n-(s[1]-h[1])*i);for(let n=0;n<a.length-1;n++)(a[n][0]-a[n+1][0])**2+(a[n][1]-a[n+1][1])**2>=.001&&s===!t.inside([(a[n][0]+a[n+1][0])/2,(a[n][1]+a[n+1][1])/2])&&e.push(a[n],a[n+1])}}return(this.dp=e).length>0}segment_intersect(t,s,e,n){const h=(n[1]-e[1])*(s[0]-t[0])-(n[0]-e[0])*(s[1]-t[1]);if(0===h)return!1;const o=((n[0]-e[0])*(t[1]-e[1])-(n[1]-e[1])*(t[0]-e[0]))/h,a=((s[0]-t[0])*(t[1]-e[1])-(s[1]-t[1])*(t[0]-e[0]))/h;return o>=0&&o<=1&&a>=0&&a<=1&&[t[0]+o*(s[0]-t[0]),t[1]+o*(s[1]-t[1])]}};return{list:()=>t,create:()=>new n,draw:(n,h,o=!0)=>{reducedPolygonList=function(n){const h={},o=200/s;for(var a=0;a<s;a++){const c=a*o-100,r=[0,c,200,c+o];if(!(n[3]<r[1]||n[1]>r[3]))for(var i=0;i<s;i++){const c=i*o-100;r[0]=c,r[2]=c+o,n[0]>r[2]||n[2]<r[0]||e[i+a*s].forEach(s=>{const e=t[s];n[3]<e.aabb[1]||n[1]>e.aabb[3]||n[0]>e.aabb[2]||n[2]<e.aabb[0]||(h[s]=1)})}}return Array.from(Object.keys(h),s=>t[s])}(h.aabb);for(let t=0;t<reducedPolygonList.length&&h.boolean(reducedPolygonList[t]);t++);h.draw(n),o&&function(n){t.push(n);const h=t.length-1,o=200/s;e.forEach((t,e)=>{const a=e%s*o-100,i=(e/s|0)*o-100,c=[a,i,a+o,i+o];c[3]<n.aabb[1]||c[1]>n.aabb[3]||c[0]>n.aabb[2]||c[2]<n.aabb[0]||t.push(h)})}(h)}}} //////////////////////////////////////////////////////////////// // 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)} // Random with seed var rng; function random() { if (rng === undefined) rng = new RNG(seed_t); return rng.nextFloat(); } function RNG(t){return new class{constructor(t){this.m=2147483648,this.a=1103515245,this.c=12345,this.state=t||Math.floor(Math.random()*(this.m-1))}nextFloat(){return this.state=(this.a*this.state+this.c)%this.m,this.state/(this.m-1)}}(t)} //////////////////////////////////////////////////////////////// // Projection from reinder's https://turtletoy.net/turtle/b3acf08303 let viewProjectionMatrix; function setupCamera(t,e){const m=lookAt4m(t,e,[0,0,1]),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) { const len = len3(a); return scale3(a,len3<0.0001?1:1/len); } function scale3(a,b) { return [a[0]*b,a[1]*b,a[2]*b]; } function len3(a) { return Math.sqrt(dot3(a,a)); } function add3(a,b) { return [a[0]+b[0],a[1]+b[1],a[2]+b[2]]; } 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]]; } function mulf(v, f) { return [v[0]*f,v[1]*f,v[2]*f]; }