Added spatial partitioning (binning) to Reinder's excellent Polygon Clipping utility to improve clipping efficiency.
Compare Faster mountains (variation) to Basic mountains (variation) to experience the difference.
Log in to post a comment.
// Forked from "Basic mountains" by llemarie // https://turtletoy.net/turtle/cfe3e80c0c // LL 2021 const seed = 0; // min=0 max=100 step=1 const subdiv_x = 400; // min=1 max=800 step=1 const subdiv_y = 200; // min=1 max=400 step=1 const scale = 150; // min=1 max=300 step=1 const height = 1; // min=0 max=2 step=0.01 const altitude = 0.7; // min=0 max=1 step=0.1 const perspective = 0.2; // min=0 max=1 step=0.01 const isometry = 0.2; // min=-1 max=1 step=0.01 const terrace = 0.0001; // min=0.0001 max=0.3 step=0.0001 const style = 1; // min=0 max=1 step=1 (Fast,Slow) const show_grid = 0; // min=0 max=1 step=1 (No,Yes) const offset_y = 30; Canvas.setpenopacity(1); const turtle = new Turtle(); var polygons = null; console.clear(); var mix_t = 1; class Mountain { contructor() { this.grid = []; this.next_index = 0; } init() { const size_x = subdiv_x + 1; const size_y = subdiv_y + 1; const multx=rng.nextFloat() * 2.5 + 0.5; const multy=rng.nextFloat() * 2.5 + 0.5; this.grid = Array.from({length: size_x*size_y}, (_,id) => { const x = (id % size_x) / subdiv_x; const y = Math.floor(id / size_x) / subdiv_y; const ax = x * Math.PI * 2; const ay = y * Math.PI * 2; var peak = 0; for (var i=1; i<=32; i*=2) { peak += Math.cos(ax*i*multx) * Math.cos(ay*i*multy) / i; } peak *= 0.4; peak = smin(peak, 0.3 + rng.nextFloat()*.005, 0.1); peak = smax(peak, 0, 0.1); const bell_spread = 0.25 * mix_t; peak *= bell(x-0.5, 1, 0, bell_spread) * bell(y-0.5, 1, 0, bell_spread); const wave_frequency = 6, wave_height = 0.01; peak = smax(peak, Math.sin(ax * wave_frequency) * Math.sin(ay * wave_frequency) * wave_height, 0.1); peak = Math.round(peak / terrace) * terrace; const z = peak * height * mix_t; return [ x - 0.5, -y, z ]; }); this.next_index = 0; } draw() { if ((this.next_index % (subdiv_x+1)) == subdiv_x) this.next_index++; const cell_count = 1; if ((this.next_index + cell_count + subdiv_x + 1) > this.grid.length) return false; const points = []; for (var j = 0; j <= cell_count; j++) { const k = j + this.next_index; points.push([ this.grid[k][0], this.grid[k][1], this.grid[k][2] ]); points.unshift([ this.grid[k + (subdiv_x+1)][0], this.grid[k + (subdiv_x+1)][1], this.grid[k + (subdiv_x+1)][2] ]); } this.next_index += cell_count; points.forEach(p => { p[0] *= 1 + perspective * p[1]; p[0] += isometry * p[1] + isometry * 0.5; p[0] *= scale; p[1] += 0.5; p[1] *= altitude; p[1] -= p[2]; p[1] *= scale; p[1] += offset_y; }); if (style == 0) { turtle.jump(points[points.length-1]); points.forEach(p=>turtle.goto(p)); } else { const p1 = polygons.create(); p1.addPoints(...points); //p1.addHatching(-Math.PI/4, 1); if (show_grid) p1.addOutline(); else p1.addSegments(p1.cp[0], p1.cp[1]); polygons.draw(turtle, p1, true); } return true; } } function clamp(x, min, max) { return Math.min(max, Math.max(min, x)); } function mix(x, y, a) { return x * (1-a) + y * a; } function smin(a, b , s) { var h = clamp( 0.5 + 0.5*(b-a)/s, 0. , 1.); return mix(b, a, h) - h*(1.0-h)*s; } function smax(a, b , s) { return -smin(-a, -b, s); } // A: amplitude, B: phase, C: spread function bell(x, a, b, c) { if (c < 0.0001) return 0; return a * Math.exp(((x-b)**2) / -(2*(c**2))); } const mountain = new Mountain(); function walk(i, t) { if (i==0) { mix_t = Math.cos(Math.pow(t,0.3)*Math.PI*2) * 0.5 + 0.5; polygons = new Polygons(); mountain.init(); } return mountain.draw(); } //// function sleep(milliseconds) { const date = Date.now(); let currentDate = null; do { currentDate = Date.now(); } while (currentDate - date < milliseconds); } //// Random with seed function RNG(_seed) { // LCG using GCC's constants this.m = 0x80000000; // 2**31; this.a = 1103515245; this.c = 12345; this.state = _seed ? _seed : Math.floor(Math.random() * (this.m - 1)); } RNG.prototype.nextFloat = function() { // returns in range [0,1] this.state = (this.a * this.state + this.c) % this.m; return this.state / (this.m - 1); } var rng = new RNG(seed); //////////////////////////////////////////////////////////////// // Polygon Clipping utility code - Created by Reinder Nijhoff 2019 // (Polygon binning by Lionel Lemarie 2021) // https://turtletoy.net/turtle/a5befa1f8d //////////////////////////////////////////////////////////////// function Polygons() { const fullPolygonList = []; const bin_size = 50; const bins = Array.from({length: bin_size**2}, (_) => []); const Polygon = class { constructor() { this.cp = []; // clip path: array of [x,y] pairs this.dp = []; // 2d lines [x0,y0],[x1,y1] to draw this.aabb = []; // AABB bounding box } addPoints(...points) { // add point to clip path and update bounding box let xmin = 1e5, xmax = -1e5, ymin = 1e5, ymax = -1e5; (this.cp = [...this.cp, ...points]).forEach( p => { xmin = Math.min(xmin, p[0]), xmax = Math.max(xmax, p[0]); ymin = Math.min(ymin, p[1]), ymax = Math.max(ymax, p[1]); }); this.aabb = [xmin, ymin, xmax, ymax]; } addSegments(...points) { // add segments (each a pair of points) points.forEach(p => this.dp.push(p)); } addOutline() { for (let i = 0, l = this.cp.length; i < l; i++) { this.dp.push(this.cp[i], this.cp[(i + 1) % l]); } } draw(t) { for (let i = 0, l = this.dp.length; i < l; i+=2) { t.jump(this.dp[i]), t.goto(this.dp[i + 1]); } } addHatching(a, d) { const tp = new Polygon(); tp.cp.push([-1e5,-1e5],[1e5,-1e5],[1e5,1e5],[-1e5,1e5]); const dx = Math.sin(a) * d, dy = Math.cos(a) * d; const cx = Math.sin(a) * 200, cy = Math.cos(a) * 200; for (let i = 0.5; i < 150 / d; i++) { tp.dp.push([dx * i + cy, dy * i - cx], [dx * i - cy, dy * i + cx]); tp.dp.push([-dx * i + cy, -dy * i - cx], [-dx * i - cy, -dy * i + cx]); } tp.boolean(this, false); this.dp = [...this.dp, ...tp.dp]; } inside(p) { let int = 0; // find number of i ntersection points from p to far away for (let i = 0, l = this.cp.length; i < l; i++) { if (this.segment_intersect(p, [0.1, -1000], this.cp[i], this.cp[(i + 1) % l])) { int++; } } return int & 1; // if even your outside } boolean(p, diff = true) { // polygon diff algorithm (narrow phase) const ndp = []; for (let i = 0, l = this.dp.length; i < l; i+=2) { const ls0 = this.dp[i]; const ls1 = this.dp[i + 1]; // find all intersections with clip path const int = []; for (let j = 0, cl = p.cp.length; j < cl; j++) { const pint = this.segment_intersect(ls0, ls1, p.cp[j], p.cp[(j + 1) % cl]); if (pint !== false) { int.push(pint); } } if (int.length === 0) { // 0 intersections, inside or outside? if (diff === !p.inside(ls0)) { ndp.push(ls0, ls1); } } else { int.push(ls0, ls1); // order intersection points on line ls.p1 to ls.p2 const cmpx = ls1[0] - ls0[0]; const cmpy = ls1[1] - ls0[1]; int.sort( (a,b) => (a[0] - ls0[0]) * cmpx + (a[1] - ls0[1]) * cmpy - (b[0] - ls0[0]) * cmpx - (b[1] - ls0[1]) * cmpy); for (let j = 0; j < int.length - 1; j++) { if ((int[j][0] - int[j+1][0])**2 + (int[j][1] - int[j+1][1])**2 >= 0.001) { if (diff === !p.inside([(int[j][0]+int[j+1][0])/2,(int[j][1]+int[j+1][1])/2])) { ndp.push(int[j], int[j+1]); } } } } } return (this.dp = ndp).length > 0; } //port of http://paulbourke.net/geometry/pointlineplane/Helpers.cs segment_intersect(l1p1, l1p2, l2p1, l2p2) { const d = (l2p2[1] - l2p1[1]) * (l1p2[0] - l1p1[0]) - (l2p2[0] - l2p1[0]) * (l1p2[1] - l1p1[1]); if (d === 0) return false; const n_a = (l2p2[0] - l2p1[0]) * (l1p1[1] - l2p1[1]) - (l2p2[1] - l2p1[1]) * (l1p1[0] - l2p1[0]); const n_b = (l1p2[0] - l1p1[0]) * (l1p1[1] - l2p1[1]) - (l1p2[1] - l1p1[1]) * (l1p1[0] - l2p1[0]); const ua = n_a / d; const ub = n_b / d; if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) { return [l1p1[0] + ua * (l1p2[0] - l1p1[0]), l1p1[1] + ua * (l1p2[1] - l1p1[1])]; } return false; } }; function getBinnedPolygonList(aabb) { const indexList = {}; const width = 200 / bin_size; for (var bi=0; bi<bin_size; bi++) { const y = bi * width - 100; const b_aabb = [0, y, 200, y + width]; if (aabb[3]<b_aabb[1] || aabb[1]>b_aabb[3]) continue; for (var bj=0; bj<bin_size; bj++) { const x = bj * width - 100; b_aabb[0] = x, b_aabb[2] = x + width; if (aabb[0]>b_aabb[2] || aabb[2]<b_aabb[0]) continue; bins[bj + bi * bin_size].forEach(id => { const p = fullPolygonList[id]; if (!(aabb[3]<p.aabb[1] || aabb[1]>p.aabb[3] || aabb[0]>p.aabb[2] || aabb[2]<p.aabb[0])) { indexList[id] = 1; } }); } } const reducedPolygonList = Array.from(Object.keys(indexList), id => fullPolygonList[id]); return reducedPolygonList; }; function addToBins(p) { fullPolygonList.push(p); const id = fullPolygonList.length - 1, width = 200 / bin_size; bins.forEach((b,i) => { const x = (i % bin_size) * width - 100; const y = (i / bin_size | 0) * width - 100; const aabb = [x, y, x + width, y + width]; if (!(aabb[3]<p.aabb[1] || aabb[1]>p.aabb[3] || aabb[0]>p.aabb[2] || aabb[2]<p.aabb[0])) { b.push(id); } }); }; return { list: () => fullPolygonList, create: () => new Polygon(), draw: (turtle, p, addToVisList=true) => { reducedPolygonList = getBinnedPolygonList(p.aabb); for (let j = 0; j < reducedPolygonList.length && p.boolean(reducedPolygonList[j]); j++); p.draw(turtle); if (addToVisList) addToBins(p); } }; }