Actually the island looks quite nice with just the heightmap without marching cubes... It almost looks like there are trees on it, which is unintentional.
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 = 3; // 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 = 11; // min=0.1 max=40 step=0.1 const look_at_z = 4; // min=-100 max=100 step=0.1 const inside_lines = 0.25; // min=0 max=1 step=0.01 const style = 2; // min=0 max=3 step=1 (Preview,All outlines,Silhouette,Hatched) const seed = 0; // min=0 max=100 step=1 //Canvas.setpenopacity((style==0) ? 0.25 : 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) 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(); console.log(`Models :${silhouette.modelCount()}. Faces: ${silhouette.faceCount()}.`); } function initOnce(t) { seed_t = (t < 1 && seed == 0) ? (Math.random() * 100 | 0) : seed; rng = undefined; initScene(); silhouette.processOnceModels(); } function initScene() { const count = 512; const mdl = makeMountains(count); var matrix = new Matrix(); matrix.scale(0.25); mdl.transform(matrix); silhouette.addModel(mdl); } ///////////////////////////////////////////////////////////////////// // Prototype Silhouette utility code - Created by Lionel Lemarie 2021 // https://turtletoy.net/turtle/334500a2c5 function Silhouette(){const t=[],s=[];class e{constructor(){this.faces=[],this.edges=[],this.hide_overlap=!0}transform(t){this.faces.forEach(s=>{s.transform(t)})}processOnce(){detail&&style&&this.subdivideDetail(detail_max_length),this.updateEdgeList()}processFrame(){this.faces.forEach(t=>{t.projectAndAdd()}),this.findStaticOutlines(),this.findProjectedOutlines(),this.updateOutlineMasks()}updateOutlineMasks(){this.edges.forEach(t=>{t.faces.forEach(s=>{const e=s.points.length;if(e>1)for(var o=0;o<e;o++){const n=i(s.points[o]),h=i(s.points[(o+1)%e]);t.hash!=n+h&&t.hash!=h+n||(t.state?s.outline_mask|=1<<o:s.outline_mask&=~(1<<o))}})})}addFace(t){const s=new o(t);this.faces.push(s)}merge(t){t.faces.forEach(t=>{this.faces.push(t)})}subdivideDetail(t){for(var s=this.faces.length,e=!0;e;){const i=this.faces;this.faces=[],i.forEach(s=>{const e=s.getSubdivided(t);this.faces=[...this.faces,...e]}),e=this.faces.length!=s,s=this.faces.length}}subdivideCount(t){for(;t-- >0;){const t=this.faces;this.faces=[],t.forEach(t=>{const s=t.getSubdivided(0);this.faces=[...this.faces,...s]})}}updateEdgeList(){this.edges=[];const t={};this.faces.forEach(s=>{const e=s.points.length;if(e>1)for(var o=0;o<e;o++){const n=i(s.points[o]),h=i(s.points[(o+1)%e]);if(n+h in t){const e=t[n+h];this.addFaceToEdge(e,s)}else if(h+n in t){const e=t[h+n];this.addFaceToEdge(e,s)}else{const e=this.edges.length;t[n+h]=e,this.edges.push({faces:[s],hash:n+h,state:1})}}})}addFaceToEdge(t,s){var e=!0;this.edges[t].faces.forEach(t=>{t.matches(s)&&(e=!1,this.hide_overlap&&(t.overlap=!0),s.overlap=!0)}),e&&this.edges[t].faces.push(s)}findStaticOutlines(){this.edges.forEach(t=>{t.state=1;for(var s=0,e=t.faces.length;s<e&&t.state;s++){if(t.faces[s].overlap)continue;const o=t.faces[s].getStaticNormal();for(var i=s+1;i<e&&t.state;i++){if(t.faces[i].overlap)continue;const s=t.faces[i].getStaticNormal();len3(sub3(o,s))<2*inside_lines&&(t.state=0)}}})}findProjectedOutlines(){this.edges.forEach(t=>{var s=!1,e=!1;t.faces.forEach(t=>{if(!t.overlap){const i=t.getProjectedNormal(),o=.001;i[2]<o&&(e=!0),i[2]>=-o&&(s=!0)}}),s&&e&&(t.state=1)})}}function i(t){return`${Math.round(100*t[0])},${Math.round(100*t[1])},${Math.round(100*t[2])}`}class o{constructor(t){this.points=[...t],this.z=0,this.projected_points=[],this.outline_mask=-1,this.overlap=!1}draw(){if(!(this.projected_points.length<2||this.overlap)){var t=!0;if(this.projected_points.forEach(s=>t&=Math.min(Math.abs(s[0]),Math.abs(s[1]))<100),t)if(0==style)for(var s=0;s<this.projected_points.length;s++)this.outline_mask&1<<s&&(turtle.jump(this.projected_points[s]),turtle.goto(this.projected_points[(s+1)%this.projected_points.length]));else{const t=polygons.create();if(t.addPoints(...this.projected_points),1==style)t.addOutline();else if(style>1){for(s=0;s<this.projected_points.length;s++)this.outline_mask&1<<s&&t.addSegments(t.cp[s],t.cp[(s+1)%this.projected_points.length]);if(style>2){const s=.15,e=s+(.9-s)*this.getLight();t.addHatching(-Math.PI/4,e)}}polygons.draw(turtle,t,!0)}}}getStaticNormal(){return void 0===this.cached_static_normal&&(this.points.length<3?this.cached_static_normal=[0,1,0]:this.cached_static_normal=normalize3(cross3(sub3(this.points[1],this.points[0]),sub3(this.points[2],this.points[0])))),this.cached_static_normal}getProjectedNormal(){return this.projected_points.length<3?[0,1,0]:normalize3(cross3(sub3(this.projected_points[1],this.projected_points[0]),sub3(this.projected_points[2],this.projected_points[0])))}getLight(){return.5*this.getProjectedNormal()[0]+.5}transform(t){for(var s=0,e=this.points.length;s<e;s++)this.points[s]=t.transform(this.points[s])}projectAndAdd(){this.overlap||(this.projected_points=[],this.z=0,this.points.forEach(t=>{const s=project(t);void 0!==s&&(this.projected_points.push(s),this.z+=s[2])}),this.projected_points.length>0&&(this.z/=this.projected_points.length),silhouette.addFace(this))}getSubdivided(t){var s=-1;const e=this.points.length;for(var i=0;i<e;i++){const o=len3(sub3(this.points[(i+1)%e],this.points[i]));o>t&&(t=o,s=i)}if(s>=0){if(4==e){const t=[];return t.push(new o([this.points[0],this.points[1],this.points[2]])),t.push(new o([this.points[2],this.points[3],this.points[0]])),t}{const t=[],n=mulf(add3(this.points[(s+1)%e],this.points[s]),.5);for(i=0;i<e;i++)i!=s&&t.push(new o([this.points[i],this.points[(i+1)%e],n]));return t}}return[this]}matches(t){const s=t.points.length;if(s!=this.points.length)return!1;if(s<1)return!0;var e=void 0;{const n=i(this.points[0]);for(var o=0;o<s;o++){if(n==i(t.points[o])){e=o;break}}}if(void 0===e)return!1;for(o=1;o<s;o++){const h=(e-o+s)%s;if(i(this.points[o])!=i(t.points[h])){for(var n=1;n<s;n++){const o=(e+n)%s;if(i(this.points[n])!=i(t.points[o]))return!1}return!0}}return!0}}return{addModel:s=>{t.push(s)},newModel:()=>new e,processFrameModels:()=>{t.forEach(t=>{t.processFrame()})},processOnceModels:()=>{t.forEach(t=>{t.processOnce()})},modelCount:()=>t.length,sortFaces:()=>{s.sort(function(t,s){return t.z-s.z})},nextFace:()=>s.shift(),faceCount:()=>s.length,addFace:t=>{s.push(t)}}} class Matrix{constructor(){this.identity()}identity(){return this.matrix=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],this}translate(t,i,r){const a=new Matrix;return a.matrix[12]=t,a.matrix[13]=i,a.matrix[14]=r,this.multiply(a)}scale(t,i,r){void 0===i&&(i=t),void 0===r&&(r=i);const a=new Matrix;return a.matrix[0]*=t,a.matrix[5]*=i,a.matrix[10]*=r,this.multiply(a)}rotateX(t){const i=new Matrix;return i.matrix[5]=Math.cos(t),i.matrix[9]=Math.sin(t),i.matrix[6]=-Math.sin(t),i.matrix[10]=Math.cos(t),this.multiply(i)}rotateY(t){const i=new Matrix;return i.matrix[0]=Math.cos(t),i.matrix[8]=-Math.sin(t),i.matrix[2]=Math.sin(t),i.matrix[10]=Math.cos(t),this.multiply(i)}rotateZ(t){const i=new Matrix;return i.matrix[0]=Math.cos(t),i.matrix[1]=-Math.sin(t),i.matrix[4]=Math.sin(t),i.matrix[5]=Math.cos(t),this.multiply(i)}multiply(t){const i=[...this.matrix],r=[...t.matrix];this.matrix=[];for(let t=0;16>t;t+=4)for(let a=0;4>a;a++)this.matrix[t+a]=i[t+0]*r[0+a]+i[t+1]*r[4+a]+i[t+2]*r[8+a]+i[t+3]*r[12+a];return this}transform(t){const i=[...t];for(let r=0;r<3;r++)i[r]=this.matrix[r]*t[0]+this.matrix[r+4]*t[1]+this.matrix[r+8]*t[2]+this.matrix[r+12];return i}} 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 += .05 * 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 model = silhouette.newModel(); const size = 256; for (var z = 0; z < count-1; z++) { const z0 = -size/2 + size / count * z; const z1 = -size/2 + size / count * (z+1); for (var x = 0; x < count-1; x++) { const x0 = -size/2 + size / count * x; const x1 = -size/2 + size / count * (x+1); const h00 = heightField([x0, z0]) * 6; const h01 = heightField([x0, z1]) * 6; const h10 = heightField([x1, z0]) * 6; const h11 = heightField([x1, z1]) * 6; if (h00 || h01 || h10 || h11) { const p00 = [x0, h00, z0]; const p01 = [x0, h01, z1]; const p10 = [x1, h10, z0]; const p11 = [x1, h11, z1]; const points = [ p00, p10, p11, p01 ]; model.addFace(points); } } } return model; } 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]; }