Silhouette Island II

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]; }