Sfoglia il codice sorgente

Add `SDFGeometryGenerator` addon. (#26837)

* add SDFGeometry ammend

* remove comment

* Update webgl_geometry_sdf.jpg

* pass that check....

* PR fixes

~ Code scanning checks
~ Update class name to SDFGeometryGenerator
~ Change class design
~ Remove confirm dialog
~ Standardize example structure
~ Remove shader editor

* 📺🔫

* Update SDFGeometryGenerator.js

- Remove extends Buffergeometry
- Replace logs by error
- Added option for more res if device supports 32k texture
- Remove unused properties

* semi missing

* move surface nets to jsm/libs

* Update SDFGeometryGenerator.js

Improve code style.

* Update SDFGeometryGenerator.js

Remove `maxTexSize` property.

* Update SDFGeometryGenerator.html

Improve doc page.

* Update webgl_geometry_sdf.html

* Update to example

Remove large glsl examples
Take screenshot

* Update SDFGeometryGenerator.js

More clean up.

* Update webgl_geometry_sdf.html

Clean up.

* Update webgl_geometry_sdf.html

Use `dispose()` methods.

* Update webgl_geometry_sdf.html

Avoid using textarea.

* Update webgl_geometry_sdf.html

Improve code style.

* Update SDFGeometryGenerator.js

Fix memory leak.

* Update SDFGeometryGenerator.js

Simplify usage of `setIndex()`.

* Remove w from position buffer

---------

Co-authored-by: Michael Herzog <[email protected]>
santi 1 anno fa
parent
commit
eb073dd25d

+ 70 - 0
docs/examples/en/geometries/SDFGeometryGenerator.html

@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<base href="../../../" />
+		<script src="page.js"></script>
+		<link type="text/css" rel="stylesheet" href="page.css" />
+	</head>
+	<body>
+
+		<h1>[name]</h1>
+
+		<p class="desc">
+			[name] generates instances of [page:BufferGeometry] from a Signed Distance Function</br>
+            Uses <a href="https://www.npmjs.com/package/isosurface" target="_blank" > Mikola Lysenko's Isosurface</a>
+		</p>
+
+		<h2>Import</h2>
+
+		<p>
+			[name] is an add-on, and must be imported explicitly.
+			See [link:#manual/introduction/Installation Installation / Addons].
+		</p>
+
+		<code>
+			import { SDFGeometryGenerator } from 'three/addons/geometries/SDFGeometryGenerator.js';
+		</code>
+
+		<h2>Code Example</h2>
+
+		<code>
+		const generator = new SDFGeometryGenerator( renderer );
+		const sdf = 'float dist( vec3 p ){ return length(p) - 0.5; }' // glsl
+		const geometry = generator.generate( 64, sdf, 1 ); // ~> THREE.BufferGeometry
+		</code>
+
+		<h2>Examples</h2>
+
+		<p>[example:webgl_geometry_sdf geometry / sdf ]</p>
+
+		<h2>Constructor</h2>
+
+		<h3>[name]( [param:WebGLRenderer renderer] )</h3>
+		
+		<p>
+			[page:WebGLRenderer renderer] -- The renderer used to render the scene. <br />
+		</p>
+
+		<h2>Methods</h2>
+
+		<h3>[method:BufferGeometry generate]( [param:Int resolution], [param:String distanceField], [param:Int bounds] )</h3>
+		
+		<p>
+			<b>resolution</b> - Int [ mandatory ] Amount of 'voxels' used for triangulation. Must be power of 2. <br/>Gets heavy after 256, most machines won't be able to process over 512. Defaults to 64. 
+		</p>
+		<p>
+			<b>distanceField</b> - String [ mandatory ] String with glsl distance function. Name of function must be 'dist', with a vec3 argument. ( see code above ). Defaults to a sphere distance.
+		</p>
+		<p>
+			<b>bounds</b> - Int [ optional ] Bounds in which signed distance field will be evaluated. Defaults to 1.
+		</p>
+
+
+		<h2>Source</h2>
+
+		<p>
+			[link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/geometries/SDFGeometry.js examples/jsm/geometries/SDFGeometryGenerator.js]
+		</p>
+	</body>
+</html>

+ 2 - 1
docs/list.json

@@ -344,7 +344,8 @@
 				"ConvexGeometry": "examples/en/geometries/ConvexGeometry",
 				"ConvexGeometry": "examples/en/geometries/ConvexGeometry",
 				"DecalGeometry": "examples/en/geometries/DecalGeometry",
 				"DecalGeometry": "examples/en/geometries/DecalGeometry",
 				"ParametricGeometry": "examples/en/geometries/ParametricGeometry",
 				"ParametricGeometry": "examples/en/geometries/ParametricGeometry",
-				"TextGeometry": "examples/en/geometries/TextGeometry"
+				"TextGeometry": "examples/en/geometries/TextGeometry",
+				"SDFGeometryGenerator": "examples/en/geometries/SDFGeometryGenerator"
 			},
 			},
 
 
 			"Helpers": {
 			"Helpers": {

+ 1 - 0
examples/files.json

@@ -34,6 +34,7 @@
 		"webgl_geometry_extrude_splines",
 		"webgl_geometry_extrude_splines",
 		"webgl_geometry_minecraft",
 		"webgl_geometry_minecraft",
 		"webgl_geometry_nurbs",
 		"webgl_geometry_nurbs",
+		"webgl_geometry_sdf",
 		"webgl_geometry_shapes",
 		"webgl_geometry_shapes",
 		"webgl_geometry_spline_editor",
 		"webgl_geometry_spline_editor",
 		"webgl_geometry_teapot",
 		"webgl_geometry_teapot",

+ 144 - 0
examples/jsm/geometries/SDFGeometryGenerator.js

@@ -0,0 +1,144 @@
+/**
+ * @author santiago / @glitch_life
+ * wrapper of https://www.npmjs.com/package/isosurface by https://github.com/mikolalysenko
+ *
+ * Returns BufferGeometry from SDF
+ */
+
+import {
+	BufferAttribute,
+	BufferGeometry,
+	FloatType,
+	Mesh,
+	OrthographicCamera,
+	PlaneGeometry,
+	Scene,
+	ShaderMaterial,
+	Vector2,
+	WebGLRenderTarget
+} from 'three';
+
+import { surfaceNet } from './../libs/surfaceNet.js';
+
+class SDFGeometryGenerator {
+
+	constructor( renderer ) {
+
+		this.renderer = renderer;
+
+	}
+
+	generate( res = 64, distFunc = 'float dist( vec3 p ){ return length(p) - 0.5; }', bounds = 1 ) {
+
+		let w, h;
+		if ( res == 8 ) [ w, h ] = [ 32, 16 ];
+		else if ( res == 16 ) [ w, h ] = [ 64, 64 ];
+		else if ( res == 32 ) [ w, h ] = [ 256, 128 ];
+		else if ( res == 64 ) [ w, h ] = [ 512, 512 ];
+		else if ( res == 128 ) [ w, h ] = [ 2048, 1024 ];
+		else if ( res == 256 ) [ w, h ] = [ 4096, 4096 ];
+		else if ( res == 512 ) [ w, h ] = [ 16384, 8096 ];
+		else if ( res == 1024 ) [ w, h ] = [ 32768, 32768 ];
+		else throw new Error( 'THREE.SDFGeometryGenerator: Resolution must be in range 8 < res < 1024 and must be ^2' );
+
+		const maxTexSize = this.renderer.capabilities.maxTextureSize;
+
+		if ( w > maxTexSize || h > maxTexSize ) throw new Error( 'THREE.SDFGeometryGenerator: Your device does not support this resolution ( ' + res + ' ), decrease [res] param.' );
+
+		const [ tilesX, tilesY ] = [ ( w / res ), ( h / res ) ];
+
+		const sdfCompute = `
+			varying vec2 vUv;
+			uniform float tileNum;
+			uniform float bounds;
+			[#dist#]
+			void main()	{ gl_FragColor=vec4( ( dist( vec3( vUv, tileNum ) * 2.0 * bounds - vec3( bounds ) ) < 0.00001 ) ? 1.0 : 0.0 ); }
+		`;
+
+		const sdfRT = this.computeSDF( w, h, tilesX, tilesY, bounds, sdfCompute.replace( '[#dist#]', distFunc ) );
+
+		const read = new Float32Array( w * h * 4 );
+		this.renderer.readRenderTargetPixels( sdfRT, 0, 0, w, h, read );
+		sdfRT.dispose();
+
+		//
+
+		const mesh = surfaceNet( [ res, res, res ], ( x, y, z ) => {
+
+			x = ( x + bounds ) * ( res / ( bounds * 2 ) );
+			y = ( y + bounds ) * ( res / ( bounds * 2 ) );
+			z = ( z + bounds ) * ( res / ( bounds * 2 ) );
+			let p = ( x + ( z % tilesX ) * res ) + y * w + ( Math.floor( z / tilesX ) * res * w );
+			p *= 4;
+			return ( read[ p + 3 ] > 0 ) ? - 0.000000001 : 1;
+
+		}, [[ - bounds, - bounds, - bounds ], [ bounds, bounds, bounds ]] );
+
+		const ps = [], ids = [];
+		const geometry = new BufferGeometry();
+		mesh.positions.forEach( p => {
+
+			ps.push( p[ 0 ], p[ 1 ], p[ 2 ] );
+
+		} );
+		mesh.cells.forEach( p => ids.push( p[ 0 ], p[ 1 ], p[ 2 ] ) );
+		geometry.setAttribute( 'position', new BufferAttribute( new Float32Array( ps ), 3 ) );
+		geometry.setIndex( ids );
+
+		return geometry;
+
+	}
+
+	computeSDF( width, height, tilesX, tilesY, bounds, shader ) {
+
+		const rt = new WebGLRenderTarget( width, height, { type: FloatType } );
+		const scn = new Scene();
+		const cam = new OrthographicCamera();
+		const tiles = tilesX * tilesY;
+		let currentTile = 0;
+
+		Object.assign( cam, { left: width / - 2, right: width / 2, top: height / 2, bottom: height / - 2 } ).updateProjectionMatrix();
+		cam.position.z = 2;
+
+		const tileSize = width / tilesX;
+		const geometry = new PlaneGeometry( tileSize, tileSize );
+
+		while ( currentTile ++ < tiles ) {
+
+			const c = currentTile - 1;
+			const [ px, py ] = [ ( tileSize ) / 2 + ( c % tilesX ) * ( tileSize ) - width / 2, ( tileSize ) / 2 + Math.floor( c / tilesX ) * ( tileSize ) - height / 2 ];
+			const compPlane = new Mesh( geometry, new ShaderMaterial( {
+				uniforms: {
+					res: { value: new Vector2( width, height ) },
+					tileNum: { value: c / ( tilesX * tilesY - 1 ) },
+					bounds: { value: bounds }
+				},
+				vertexShader: 'varying vec2 vUv;void main(){vUv=uv;gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);}',
+				fragmentShader: shader
+			} ) );
+			compPlane.position.set( px, py, 0 );
+			scn.add( compPlane );
+
+		}
+
+		this.renderer.setRenderTarget( rt );
+		this.renderer.render( scn, cam );
+		this.renderer.setRenderTarget( null );
+
+		//
+
+		geometry.dispose();
+
+		scn.traverse( function ( object ) {
+
+			if ( object.material !== undefined ) object.material.dispose();
+
+		} );
+
+		return rt;
+
+	}
+
+}
+
+export { SDFGeometryGenerator };

+ 201 - 0
examples/jsm/libs/surfaceNet.js

@@ -0,0 +1,201 @@
+/**
+ * SurfaceNets in JavaScript
+ *
+ * Written by Mikola Lysenko (C) 2012
+ *
+ * MIT License
+ *
+ * Based on: S.F. Gibson, 'Constrained Elastic Surface Nets'. (1998) MERL Tech Report.
+ * from https://github.com/mikolalysenko/isosurface/tree/master
+ * 
+ */
+
+let surfaceNet = ( dims, potential, bounds ) => {
+		
+	
+	//Precompute edge table, like Paul Bourke does.
+	// This saves a bit of time when computing the centroid of each boundary cell
+	var cube_edges = new Int32Array(24) , edge_table = new Int32Array(256);
+	(function() {
+
+		//Initialize the cube_edges table
+		// This is just the vertex number of each cube
+		var k = 0;
+		for(var i=0; i<8; ++i) {
+			for(var j=1; j<=4; j<<=1) {
+				var p = i^j;
+				if(i <= p) {
+					cube_edges[k++] = i;
+					cube_edges[k++] = p;
+				}
+			}
+		}
+
+		//Initialize the intersection table.
+		//  This is a 2^(cube configuration) ->  2^(edge configuration) map
+		//  There is one entry for each possible cube configuration, and the output is a 12-bit vector enumerating all edges crossing the 0-level.
+		for(var i=0; i<256; ++i) {
+			var em = 0;
+			for(var j=0; j<24; j+=2) {
+				var a = !!(i & (1<<cube_edges[j]))
+					, b = !!(i & (1<<cube_edges[j+1]));
+				em |= a !== b ? (1 << (j >> 1)) : 0;
+			}
+			edge_table[i] = em;
+		}
+	})();
+
+	//Internal buffer, this may get resized at run time
+	var buffer = new Array(4096);
+	(function() {
+		for(var i=0; i<buffer.length; ++i) {
+			buffer[i] = 0;
+		}
+	})();
+
+	if(!bounds) {
+		bounds = [[0,0,0],dims];
+	}
+	
+	var scale     = [0,0,0];
+	var shift     = [0,0,0];
+	for(var i=0; i<3; ++i) {
+		scale[i] = (bounds[1][i] - bounds[0][i]) / dims[i];
+		shift[i] = bounds[0][i];
+	}
+	
+	var vertices = []
+		, faces = []
+		, n = 0
+		, x = [0, 0, 0]
+		, R = [1, (dims[0]+1), (dims[0]+1)*(dims[1]+1)]
+		, grid = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+		, buf_no = 1;
+	
+		
+	//Resize buffer if necessary 
+	if(R[2] * 2 > buffer.length) {
+		var ol = buffer.length;
+		buffer.length = R[2] * 2;
+		while(ol < buffer.length) {
+			buffer[ol++] = 0;
+		}
+	}
+	
+	//March over the voxel grid
+	for(x[2]=0; x[2]<dims[2]-1; ++x[2], n+=dims[0], buf_no ^= 1, R[2]=-R[2]) {
+	
+		//m is the pointer into the buffer we are going to use.  
+		//This is slightly obtuse because javascript does not have good support for packed data structures, so we must use typed arrays :(
+		//The contents of the buffer will be the indices of the vertices on the previous x/y slice of the volume
+		var m = 1 + (dims[0]+1) * (1 + buf_no * (dims[1]+1));
+		
+		for(x[1]=0; x[1]<dims[1]-1; ++x[1], ++n, m+=2)
+		for(x[0]=0; x[0]<dims[0]-1; ++x[0], ++n, ++m) {
+		
+			//Read in 8 field values around this vertex and store them in an array
+			//Also calculate 8-bit mask, like in marching cubes, so we can speed up sign checks later
+			var mask = 0, g = 0;
+			for(var k=0; k<2; ++k)
+			for(var j=0; j<2; ++j)      
+			for(var i=0; i<2; ++i, ++g) {
+				var p = potential(
+					scale[0]*(x[0]+i)+shift[0],
+					scale[1]*(x[1]+j)+shift[1],
+					scale[2]*(x[2]+k)+shift[2]);
+				grid[g] = p;
+				mask |= (p < 0) ? (1<<g) : 0;
+			}
+			
+			//Check for early termination if cell does not intersect boundary
+			if(mask === 0 || mask === 0xff) {
+				continue;
+			}
+			
+			//Sum up edge intersections
+			var edge_mask = edge_table[mask]
+				, v = [0.0,0.0,0.0]
+				, e_count = 0;
+				
+			//For every edge of the cube...
+			for(var i=0; i<12; ++i) {
+			
+				//Use edge mask to check if it is crossed
+				if(!(edge_mask & (1<<i))) {
+					continue;
+				}
+				
+				//If it did, increment number of edge crossings
+				++e_count;
+				
+				//Now find the point of intersection
+				var e0 = cube_edges[ i<<1 ]       //Unpack vertices
+					, e1 = cube_edges[(i<<1)+1]
+					, g0 = grid[e0]                 //Unpack grid values
+					, g1 = grid[e1]
+					, t  = g0 - g1;                 //Compute point of intersection
+				if(Math.abs(t) > 1e-6) {
+					t = g0 / t;
+				} else {
+					continue;
+				}
+				
+				//Interpolate vertices and add up intersections (this can be done without multiplying)
+				for(var j=0, k=1; j<3; ++j, k<<=1) {
+					var a = e0 & k
+						, b = e1 & k;
+					if(a !== b) {
+						v[j] += a ? 1.0 - t : t;
+					} else {
+						v[j] += a ? 1.0 : 0;
+					}
+				}
+			}
+			
+			//Now we just average the edge intersections and add them to coordinate
+			var s = 1.0 / e_count;
+			for(var i=0; i<3; ++i) {
+				v[i] = scale[i] * (x[i] + s * v[i]) + shift[i];
+			}
+			
+			//Add vertex to buffer, store pointer to vertex index in buffer
+			buffer[m] = vertices.length;
+			vertices.push(v);
+			
+			//Now we need to add faces together, to do this we just loop over 3 basis components
+			for(var i=0; i<3; ++i) {
+				//The first three entries of the edge_mask count the crossings along the edge
+				if(!(edge_mask & (1<<i)) ) {
+					continue;
+				}
+				
+				// i = axes we are point along.  iu, iv = orthogonal axes
+				var iu = (i+1)%3
+					, iv = (i+2)%3;
+					
+				//If we are on a boundary, skip it
+				if(x[iu] === 0 || x[iv] === 0) {
+					continue;
+				}
+				
+				//Otherwise, look up adjacent edges in buffer
+				var du = R[iu]
+					, dv = R[iv];
+				
+				//Remember to flip orientation depending on the sign of the corner.
+				if(mask & 1) {
+					faces.push([buffer[m],    buffer[m-du],    buffer[m-dv]]);
+					faces.push([buffer[m-dv], buffer[m-du],    buffer[m-du-dv]]);
+				} else {
+					faces.push([buffer[m],    buffer[m-dv],    buffer[m-du]]);
+					faces.push([buffer[m-du], buffer[m-dv],    buffer[m-du-dv]]);
+				}
+			}
+		}
+	}
+	
+	//All done!  Return the result
+	return { positions: vertices, cells: faces };
+}
+
+export { surfaceNet }

BIN
examples/screenshots/webgl_geometry_sdf.jpg


+ 214 - 0
examples/webgl_geometry_sdf.html

@@ -0,0 +1,214 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - SDF Geometry</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+	<body>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> -SDF to Mesh-<br/>
+			a wrapper of <a href="https://www.npmjs.com/package/isosurface" target="_blank" > Mikola Lysenko's Isosurface</a><br/>
+			Mandelbrot by <a href="https://www.shadertoy.com/view/MdXSWn" target="_blank" > EvilRyu</a><br/>
+		</div>
+		
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+			import * as THREE from 'three';
+			import { OrbitControls } from './jsm/controls/OrbitControls.js';
+			import { SDFGeometryGenerator } from 'three/addons/geometries/SDFGeometryGenerator.js';
+			import Stats from 'three/addons/libs/stats.module.js';
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+			
+			let renderer, stats, meshFromSDF, scene, camera, clock, controls;
+
+			const settings = {
+				res: 4,
+				bounds: 1,
+				autoRotate: true,
+				wireframe: true,
+				material: 'depth',
+				vertexCount: '0'
+			};
+
+			// Example SDF from https://www.shadertoy.com/view/MdXSWn -->
+
+			const shader = /* glsl */`
+				float dist(vec3 p) {
+					p.xyz = p.xzy;
+					p *= 1.2;
+					vec3 z = p;
+					vec3 dz=vec3(0.0);
+					float power = 8.0;
+					float r, theta, phi;
+					float dr = 1.0;
+					
+					float t0 = 1.0;
+					for(int i = 0; i < 7; ++i) {
+						r = length(z);
+						if(r > 2.0) continue;
+						theta = atan(z.y / z.x);
+						#ifdef phase_shift_on
+						phi = asin(z.z / r) ;
+						#else
+						phi = asin(z.z / r);
+						#endif
+						
+						dr = pow(r, power - 1.0) * dr * power + 1.0;
+					
+						r = pow(r, power);
+						theta = theta * power;
+						phi = phi * power;
+						
+						z = r * vec3(cos(theta)*cos(phi), sin(theta)*cos(phi), sin(phi)) + p;
+						
+						t0 = min(t0, r);
+					}
+		
+					return 0.5 * log(r) * r / dr;
+				}
+			`;
+
+			init();
+			animate();
+
+			function init() {
+
+				const w = window.innerWidth;
+				const h = window.innerHeight;
+
+				camera = new THREE.OrthographicCamera( w / - 2, w / 2, h / 2, h / - 2, 0.01, 1600 );
+				camera.position.z = 1100;
+
+				scene = new THREE.Scene();
+			
+				clock = new THREE.Clock();
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+
+				stats = new Stats();
+				document.body.appendChild( stats.dom );
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.enableDamping = true;
+
+				window.addEventListener( 'resize', onWindowResize );
+
+				//
+			
+				const panel = new GUI( );
+			
+				panel.add( settings, 'res', 1, 6, 1 ).name( 'Res' ).onFinishChange( compile );
+				panel.add( settings, 'bounds', 1, 10, 1 ).name( 'Bounds' ).onFinishChange( compile );
+				panel.add( settings, 'material', [ 'depth', 'normal' ] ).name( 'Material' ).onChange( setMaterial );
+				panel.add( settings, 'wireframe' ).name( 'Wireframe' ).onChange( setMaterial );
+				panel.add( settings, 'autoRotate' ).name( 'Auto Rotate' );
+				panel.add( settings, 'vertexCount' ).name( 'Vertex count' ).listen().disable();
+
+				//
+
+				compile();
+
+			}
+
+			function compile() {
+			
+				const generator = new SDFGeometryGenerator( renderer );
+				const geometry = generator.generate( Math.pow( 2, settings.res + 2 ), shader, settings.bounds );
+				geometry.computeVertexNormals();
+
+				if ( meshFromSDF ) { // updates mesh
+
+					meshFromSDF.geometry.dispose();
+					meshFromSDF.geometry = geometry;
+			
+				} else { // inits meshFromSDF : THREE.Mesh
+			
+					meshFromSDF = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial() );
+					scene.add( meshFromSDF );
+			
+					const scale = Math.min( window.innerWidth, window.innerHeight ) / 2 * 0.66;
+					meshFromSDF.scale.set( scale, scale, scale );
+			
+					setMaterial();
+			
+				}
+
+				settings.vertexCount = geometry.attributes.position.count;
+
+			}
+
+			function setMaterial() {
+
+				meshFromSDF.material.dispose();
+
+				if ( settings.material == 'depth' ) {
+
+					meshFromSDF.material = new THREE.MeshDepthMaterial();
+			
+				} else if ( settings.material == 'normal' ) {
+
+					meshFromSDF.material = new THREE.MeshNormalMaterial();
+			
+				}
+
+				meshFromSDF.material.wireframe = settings.wireframe;
+
+			}
+
+			function onWindowResize() {
+
+				const w = window.innerWidth;
+				const h = window.innerHeight;
+
+				renderer.setSize( w, h );
+
+				camera.left = w / - 2;
+				camera.right = w / 2;
+				camera.top = h / 2;
+				camera.bottom = h / - 2;
+
+				camera.updateProjectionMatrix();
+
+			}
+
+			function render() {
+
+				renderer.render( scene, camera );
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				controls.update();
+
+				if ( settings.autoRotate ) {
+
+					meshFromSDF.rotation.y += Math.PI * 0.05 * clock.getDelta();
+
+				}
+
+				render();
+
+				stats.update();
+
+			}
+
+		</script>
+
+	</body>
+</html>