瀏覽代碼

Added @zz85's cloth simulation example.

Cloth.js will need some cleaning but I guess it's better to let @zz85 finish it first (if more work is planned on it).
alteredq 13 年之前
父節點
當前提交
55444fc7f4
共有 4 個文件被更改,包括 724 次插入1 次删除
  1. 322 0
      examples/js/Cloth.js
  2. 二進制
      examples/textures/patterns/circuit_pattern.png
  3. 1 1
      examples/textures/patterns/readme.txt
  4. 401 0
      examples/webgl_animation_cloth.html

+ 322 - 0
examples/js/Cloth.js

@@ -0,0 +1,322 @@
+/*
+ * Aug 3 2012
+ *
+ * Since I started working for a new startup not too long ago,
+ * I commute between home and work for over 2 hours a day.
+ * Although this means less time on three.js,
+ * I try getting a little coding on the train.
+ *
+ * This set of experiments started from a simple hook's law doodle,
+ * to spring simulation, string simulation, and I realized
+ * I once again stepped onto physics and particle simulation, 
+ * this time, more specifically soft body physics.
+ *
+ * Based on the "Advanced Character Physics" article,
+ * this experiment attempts to use a "massless"
+ * cloth simulation model. It's somewhat similiar 
+ * but simplier to most cloth simulations I found.
+ *
+ * This was coded out fairly quickly, so expect more to come
+ * meanwhile feel free to experiment yourself and share
+ *
+ * Cheers,
+ * Graphics Noob (aka @Blurspline, zz85)
+ */
+
+// Suggested Readings
+
+// Advanced Character Physics by Thomas Jakobsen Character
+// http://freespace.virgin.net/hugo.elias/models/m_cloth.htm
+// http://en.wikipedia.org/wiki/Cloth_modeling
+// http://cg.alexandra.dk/tag/spring-mass-system/
+// Real-time Cloth Animation http://www.darwin3d.com/gamedev/articles/col0599.pdf
+
+var DAMPING = 0.01;
+var DRAG = 1 - DAMPING;
+var MASS = .1;
+var restDistance = 25; //
+
+function Particle(x, y, z, mass) {
+	this.position = new THREE.Vector3(x, y, z); // position
+	this.previous = new THREE.Vector3(x, y, z); // previous
+	this.a = new THREE.Vector3(0, 0, 0); // acceleration
+	this.mass = mass;
+	this.invMass = 1 / mass;
+	this.tmp = new THREE.Vector3();
+	this.tmp2 = new THREE.Vector3();
+}
+
+// Force -> Acceleration
+Particle.prototype.addForce = function(force) {
+	this.a.addSelf(
+		this.tmp2.copy(force).multiplyScalar(this.invMass)
+	);
+};
+
+
+// Performs verlet integration
+Particle.prototype.integrate = function(timesq) {
+	var newPos = this.tmp.sub(this.position, this.previous);
+	newPos.multiplyScalar(DRAG).addSelf(this.position);
+	newPos.addSelf(this.a.multiplyScalar(timesq));
+	
+	this.tmp = this.previous;
+	this.previous = this.position;
+	this.position = newPos;
+
+	this.a.set(0, 0, 0);
+}
+
+
+var diff = new THREE.Vector3();
+
+function satisifyConstrains(p1, p2, distance) {
+	diff.sub(p2.position, p1.position);
+	var currentDist = diff.length();
+	if (currentDist==0) return; // prevents division by 0
+	var correction = diff.multiplyScalar(1 - distance/currentDist);
+	var correctionHalf = correction.multiplyScalar(0.5);
+	p1.position.addSelf(correctionHalf);
+	p2.position.subSelf(correctionHalf);
+
+	// float difference = (restingDistance - d) / d
+	// im1 = 1 / p1.mass // inverse mass quantities
+	// im2 = 1 / p2.mass
+	// p1.position += delta * (im1 / (im1 + im2)) * stiffness * difference
+
+}
+
+
+function Cloth(w, h) {
+	w = w || 10;
+	h = h || 10;
+	this.w = w;
+	this.h = h;
+
+	var particles = [];
+	var constrains = [];
+
+	var u, v;
+
+	// Create particles
+	for (v=0;v<=h;v++) {
+		for (u=0;u<=w;u++) {
+			particles.push(
+				new Particle((u - w/2) * restDistance, (v - h/2) * -restDistance, 0, MASS)
+			);
+		}
+	}
+
+	// Structural
+
+	for (v=0;v<h;v++) {
+		for (u=0;u<w;u++) {
+
+			constrains.push([
+				particles[index(u, v)],
+				particles[index(u, v+1)],
+				restDistance
+			]);
+
+			constrains.push([
+				particles[index(u, v)],
+				particles[index(u+1, v)],
+				restDistance
+			]);
+
+		}
+	}
+
+	for (u=w, v=0;v<h;v++) {
+		constrains.push([
+			particles[index(u, v)],
+			particles[index(u, v+1)],
+			restDistance
+
+		]);
+	}
+
+	for (v=h, u=0;u<w;u++) {
+		constrains.push([
+			particles[index(u, v)],
+			particles[index(u+1, v)],
+			restDistance
+		]);
+	}
+
+
+	// While many system uses shear and bend springs,
+	// the relax constrains model seem to be just fine
+	// using structural springs.
+	// // Shear
+	// var diagonalDist = Math.sqrt(restDistance * restDistance * 2);
+
+
+	// for (v=0;v<h;v++) {
+	// 	for (u=0;u<w;u++) {
+
+	// 		constrains.push([
+	// 			particles[index(u, v)],
+	// 			particles[index(u+1, v+1)],
+	// 			diagonalDist
+	// 		]);
+
+	// 		constrains.push([
+	// 			particles[index(u+1, v)],
+	// 			particles[index(u, v+1)],
+	// 			diagonalDist
+	// 		]);
+
+	// 	}
+	// }
+
+
+	// // Bend
+
+	// var wlen = restDistance * 2;
+	// var hlen = restDistance * 2;
+	// diagonalDist = Math.sqrt(wlen * wlen + hlen * hlen);
+
+	// for (v=0;v<h-1;v++) {
+	// 	for (u=0;u<w-1;u++) {
+	// 		constrains.push([
+	// 			particles[index(u, v)],
+	// 			particles[index(u+2, v)],
+	// 			wlen
+	// 		]);
+
+	// 		constrains.push([
+	// 			particles[index(u, v)],
+	// 			particles[index(u, v+2)],
+	// 			hlen
+	// 		]);
+
+	// 		constrains.push([
+	// 			particles[index(u, v)],
+	// 			particles[index(u+2, v+2)],
+	// 			diagonalDist
+	// 		]);
+
+	// 		constrains.push([
+	// 			particles[index(u, v+2)],
+	// 			particles[index(u+2, v+2)],
+	// 			wlen
+	// 		]);
+
+	// 		constrains.push([
+	// 			particles[index(u+2, v+2)],
+	// 			particles[index(u+2, v+2)],
+	// 			hlen
+	// 		]);
+
+	// 		constrains.push([
+	// 			particles[index(u+2, v)],
+	// 			particles[index(u, v+2)],
+	// 			diagonalDist
+	// 		]);
+
+	// 	}
+	// }
+
+
+	this.particles = particles;
+	this.constrains = constrains;
+
+	function index(u, v) {
+		return u + v * (w + 1);
+	}
+
+
+}
+
+var cloth = new Cloth();
+
+var GRAVITY = 981; // 
+var gravity = new THREE.Vector3( 0, -GRAVITY, 0 ).multiplyScalar(MASS);
+
+
+var TIMESTEP = 14 / 1000;
+var TIMESTEP_SQ = TIMESTEP * TIMESTEP;
+
+var pins = [true];
+pins[cloth.w] = true;
+
+
+var wind = true;
+var windStrength = 2;
+var windForce = new THREE.Vector3(0,0,0);
+
+var ballPosition = new THREE.Vector3(0, -45, 0);
+var ballSize = 60; //40
+
+var tmpForce = new THREE.Vector3();
+
+function simulate() {
+	
+	var i, il, particles, particle, pt, constrains, constrain;
+
+	// Aerodynamics forces
+	if (wind) {
+		var face, faces = clothGeometry.faces, normal;
+
+		particles = cloth.particles;
+
+		for (i=0,il=faces.length;i<il;i++) {
+			face = faces[i];
+			normal = face.normal;
+
+			tmpForce.copy(normal).normalize().multiplyScalar(normal.dot(windForce));
+			particles[face.a].addForce(tmpForce);
+			particles[face.b].addForce(tmpForce);
+			particles[face.c].addForce(tmpForce);
+		}
+	}
+	
+	for (particles = cloth.particles, i=0, il = particles.length
+			;i<il;i++) {
+		particle = particles[i];
+		particle.addForce(gravity);
+		// particle.addForce(windForce);
+		particle.integrate(TIMESTEP_SQ);
+	}
+
+	// Start Constrains
+
+	constrains = cloth.constrains,
+	il = constrains.length;
+	for (i=0;i<il;i++) {
+		constrain = constrains[i];
+		satisifyConstrains(constrain[0], constrain[1], constrain[2]);
+	}
+
+	// Ball Constrains
+
+
+	ballPosition.z = -Math.sin(Date.now()/300) * 90 ; //+ 40;
+	ballPosition.x = Math.cos(Date.now()/200) * 70
+
+	if (sphere.visible)
+	for (particles = cloth.particles, i=0, il = particles.length
+			;i<il;i++) {
+		particle = particles[i];
+		pos = particle.position;
+		diff.sub(pos, ballPosition);
+		if (diff.length() < ballSize) {
+			// collided
+			diff.normalize().multiplyScalar(ballSize);
+			pos.copy(ballPosition).addSelf(diff);
+		}
+	}
+
+	// Pin Constrains
+
+	for (i=0, il=cloth.w;i<=il;i++) {
+		if (pins[i]) {
+			particle = particles[i];
+			particle.previous.set((i - cloth.w/2) * restDistance,  -cloth.h/2 * -restDistance, 0);
+			particle.position.copy(particle.previous);
+		}
+	}
+
+
+}

二進制
examples/textures/patterns/circuit_pattern.png


+ 1 - 1
examples/textures/patterns/readme.txt

@@ -1,4 +1,4 @@
-Textures from http://subtlepatterns.com/
+Texture "bright_squares256.png" from http://subtlepatterns.com/
 
 Slightly modified to have more GPU friendly sizes.
 

+ 401 - 0
examples/webgl_animation_cloth.html

@@ -0,0 +1,401 @@
+<!doctype html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - cloth simulation</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				font-family: Monospace;
+				background-color: #000;
+				color: #000;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			#info {
+				text-align: center;
+				padding: 10px;
+				z-index: 10;
+				width: 100%;
+				position: absolute;
+			}
+
+			a {
+				text-decoration: underline;
+				cursor: pointer;
+			}
+		</style>
+	</head>
+
+	<body>
+		<div id="info">Simple Cloth Simulation #3<br/>
+			Verlet based with Constrains relaxation<br/>
+			Toggle: <a onclick="rotate = !rotate;">Camera</a> |
+			<a onclick="wind = !wind;">Wind</a> |
+			<a onclick="sphere.visible = !sphere.visible;">Ball</a> |
+			<a onclick="togglePins();">Pins</a>
+		</div>
+
+		<script src="../build/Three.js"></script>
+
+		<script src="js/Detector.js"></script>
+		<script src="js/Stats.js"></script>
+
+		<script src="js/ParametricGeometries.js"></script>
+		<script src="js/Cloth.js"></script>
+
+		<script type="x-shader/x-fragment" id="fragmentShaderDepth">
+
+			uniform sampler2D texture;
+			varying vec2 vUV;
+
+			vec4 pack_depth( const in float depth ) {
+
+				const vec4 bit_shift = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );
+				const vec4 bit_mask  = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 );
+				vec4 res = fract( depth * bit_shift );
+				res -= res.xxyz * bit_mask;
+				return res;
+
+			}
+
+			void main() {
+
+				vec4 pixel = texture2D( texture, vUV );
+
+				if ( pixel.a < 0.5 ) discard;
+
+				gl_FragData[ 0 ] = pack_depth( gl_FragCoord.z );
+
+			}
+		</script>
+
+		<script type="x-shader/x-vertex" id="vertexShaderDepth">
+
+			varying vec2 vUV;
+
+			void main() {
+
+				vUV = 0.75 * uv;
+
+				vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
+
+				gl_Position = projectionMatrix * mvPosition;
+
+			}
+
+		</script>
+
+		<script>
+
+			/* testing cloth simulation */
+
+			var pinsFormation = [];
+			var pins = [];
+
+			pins[6] = true;
+			pinsFormation.push( pins );
+
+			pins = [ true, true, true, true, true, true, true, true, true, true, true, true ];
+			pinsFormation.push( pins );
+
+			pins = [ true ];
+			pinsFormation.push( pins );
+
+			pins = []; // cut the rope ;)
+			pinsFormation.push( pins );
+
+			pins = [ true ]; // classic 2 pins
+			pins[ cloth.w ] = true;
+			pinsFormation.push( pins );
+
+			pins = pinsFormation[ 1 ];
+
+
+			function togglePins() {
+
+				pins = pinsFormation[ ~~( Math.random() * pinsFormation.length ) ];
+
+			}
+
+			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+			var container, stats;
+			var camera, scene, renderer;
+
+			var clothGeometry;
+			var sphere;
+			var object, arrow;
+
+			var rotate = true;
+
+			init();
+			animate();
+
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				// scene
+
+				scene = new THREE.Scene();
+
+				scene.fog = new THREE.Fog( 0x000000, 500, 10000 );
+				scene.fog.color.setHSV( 0.6, 0.2, 1 );
+
+				// camera
+
+				camera = new THREE.PerspectiveCamera( 30, window.innerWidth / window.innerHeight, 1, 10000 );
+				camera.position.y = 50;
+				camera.position.z = 1500;
+				scene.add( camera );
+
+				// lights
+
+				var light, materials;
+
+				scene.add( new THREE.AmbientLight( 0x666666 ) );
+
+				light = new THREE.DirectionalLight( 0xffffff, 1.75 );
+				light.color.setHSV( 0.6, 0.125, 1 );
+				light.position.set( 50, 200, 100 );
+				light.position.multiplyScalar( 1.3 );
+
+				light.castShadow = true;
+				//light.shadowCameraVisible = true;
+
+				light.shadowMapWidth = 2048;
+				light.shadowMapHeight = 2048;
+
+				var d = 300;
+
+				light.shadowCameraLeft = -d;
+				light.shadowCameraRight = d;
+				light.shadowCameraTop = d;
+				light.shadowCameraBottom = -d;
+
+				light.shadowCameraFar = 1000;
+				light.shadowDarkness = 0.5;
+
+				scene.add( light );
+
+				light = new THREE.DirectionalLight( 0xffffff, 0.35 );
+				light.color.setHSV( 0.3, 0.95, 1 );
+				light.position.set( 0, -1, 0 );
+
+				scene.add( light );
+
+				// cloth material
+
+				var clothTexture = THREE.ImageUtils.loadTexture( 'textures/patterns/circuit_pattern.png' );
+				clothTexture.wrapS = clothTexture.wrapT = THREE.RepeatWrapping;
+				clothTexture.anisotropy = 16;
+
+				materials = [
+					new THREE.MeshPhongMaterial( { alphaTest: 0.5, ambient: 0xffffff, color: 0xffffff, specular: 0x030303, emissive: 0x111111, shiness: 10, perPixel: true, metal: false, map: clothTexture, doubleSided: true } ),
+					new THREE.MeshBasicMaterial( { color: 0xff0000, wireframe: true, transparent: true, opacity: 0.9 } )
+				];
+
+				// cloth geometry
+
+				clothGeometry = new THREE.ParametricGeometry( THREE.ParametricGeometries.plane( 200, 200 ), cloth.w, cloth.h, true );
+				clothGeometry.dynamic = true;
+				clothGeometry.computeFaceNormals();
+
+				var uniforms = { texture:  { type: "t", value: 0, texture: clothTexture } };
+				var vertexShader = document.getElementById( 'vertexShaderDepth' ).textContent;
+				var fragmentShader = document.getElementById( 'fragmentShaderDepth' ).textContent;
+
+				// cloth mesh
+
+				object = new THREE.Mesh( clothGeometry, materials[ 0 ] );
+				object.position.set( 0, 0, 0 );
+				object.castShadow = true;
+				object.receiveShadow = true;
+				scene.add( object );
+
+				object.customDepthMaterial = new THREE.ShaderMaterial( { uniforms: uniforms, vertexShader: vertexShader, fragmentShader: fragmentShader } );
+
+				// sphere
+
+				var ballGeo = new THREE.SphereGeometry( ballSize, 20, 20 );
+				var ballMaterial = new THREE.MeshPhongMaterial( { color: 0xffffff } );
+
+				sphere = new THREE.Mesh( ballGeo, ballMaterial );
+				sphere.castShadow = true;
+				sphere.receiveShadow = true;
+				scene.add( sphere );
+
+				// arrow
+
+				arrow = new THREE.ArrowHelper( new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, 0, 0 ), 50, 0xff0000 );
+				arrow.position.set( -200, 0, -200 );
+				//scene.add( arrow );
+
+				var axis;
+				axis = new THREE.AxisHelper();
+				axis.position.set( 200, 0, -200 );
+				axis.scale.x = axis.scale.y = axis.scale.z = 0.5;
+				//scene.add( axis );
+
+				// ground
+
+				var initColor = new THREE.Color( 0x00ff00 );
+				initColor.setHSV( 0.25, 0.85, 0.5 );
+				var initTexture = THREE.ImageUtils.generateDataTexture( 1, 1, initColor );
+
+				var groundMaterial = new THREE.MeshPhongMaterial( { color: 0xffffff, specular: 0x111111, map: initTexture, perPixel: true } );
+
+				var groundTexture = THREE.ImageUtils.loadTexture( "textures/terrain/grasslight-big.jpg", undefined, function() { groundMaterial.map = groundTexture } );
+				groundTexture.wrapS = groundTexture.wrapT = THREE.RepeatWrapping;
+				groundTexture.repeat.set( 25, 25 );
+				groundTexture.anisotropy = 16;
+
+				var mesh = new THREE.Mesh( new THREE.PlaneGeometry( 20000, 20000 ), groundMaterial );
+				mesh.position.y = -250;
+				mesh.rotation.x = - Math.PI / 2;
+				mesh.receiveShadow = true;
+				scene.add( mesh );
+
+				// poles
+
+				var poleGeo = new THREE.CubeGeometry( 5, 750, 5 );
+				var poleMat = new THREE.MeshPhongMaterial( { color: 0xffffff, specular: 0x111111, shiness: 100, perPixel: true } );
+
+				var mesh = new THREE.Mesh( poleGeo, poleMat );
+				mesh.position.y = -250;
+				mesh.position.x = -125;
+				mesh.receiveShadow = true;
+				mesh.castShadow = true;
+				scene.add( mesh );
+
+				var mesh = new THREE.Mesh( poleGeo, poleMat );
+				mesh.position.y = -250;
+				mesh.position.x = 125;
+				mesh.receiveShadow = true;
+				mesh.castShadow = true;
+				scene.add( mesh );
+
+				var mesh = new THREE.Mesh( new THREE.CubeGeometry( 255, 5, 5 ), poleMat );
+				mesh.position.y = -250 + 750/2;
+				mesh.position.x = 0;
+				mesh.receiveShadow = true;
+				mesh.castShadow = true;
+				scene.add( mesh );
+
+				var gg = new THREE.CubeGeometry( 10, 10, 10 );
+				var mesh = new THREE.Mesh( gg, poleMat );
+				mesh.position.y = -250;
+				mesh.position.x = 125;
+				mesh.receiveShadow = true;
+				mesh.castShadow = true;
+				scene.add( mesh );
+
+				var mesh = new THREE.Mesh( gg, poleMat );
+				mesh.position.y = -250;
+				mesh.position.x = -125;
+				mesh.receiveShadow = true;
+				mesh.castShadow = true;
+				scene.add( mesh );
+
+				//
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setClearColor( scene.fog.color );
+
+				container.appendChild( renderer.domElement );
+
+				renderer.gammaInput = true;
+				renderer.gammaOutput = true;
+				renderer.physicallyBasedShading = true;
+
+				renderer.shadowMapEnabled = true;
+
+				//
+
+				stats = new Stats();
+				stats.domElement.style.position = 'absolute';
+				stats.domElement.style.top = '0px';
+				container.appendChild( stats.domElement );
+
+				stats.domElement.children[ 0 ].children[ 0 ].style.color = "#aaa";
+				stats.domElement.children[ 0 ].style.background = "transparent";
+				stats.domElement.children[ 0 ].children[ 1 ].style.display = "none";
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+				sphere.visible = !true
+
+			}
+
+			//
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				var time = Date.now();
+
+				windStrength = Math.cos( time / 7000 ) * 20 + 40;
+				windForce.set( Math.sin( time / 2000 ), Math.cos( time / 3000 ), Math.sin( time / 1000 ) ).normalize().multiplyScalar( windStrength );
+				arrow.setLength( windStrength );
+				arrow.setDirection( windForce );
+
+				simulate();
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				var timer = Date.now() * 0.0002;
+
+				var p = cloth.particles;
+
+				for ( var i = 0, il = p.length; i < il; i ++ ) {
+
+					clothGeometry.vertices[ i ].copy( p[ i ].position );
+
+				}
+
+				clothGeometry.computeFaceNormals();
+				clothGeometry.computeVertexNormals();
+
+				clothGeometry.normalsNeedUpdate = true;
+				clothGeometry.verticesNeedUpdate = true;
+
+				sphere.position.copy( ballPosition );
+
+				if ( rotate ) {
+
+					camera.position.x = Math.cos( timer ) * 1500;
+					camera.position.z = Math.sin( timer ) * 1500;
+
+				}
+
+				camera.lookAt( scene.position );
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>