提交 55444fc7 编写于 作者: A alteredq

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).
上级 ee7d002a
/*
* 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);
}
}
}
\ No newline at end of file
Textures from http://subtlepatterns.com/
Texture "bright_squares256.png" from http://subtlepatterns.com/
Slightly modified to have more GPU friendly sizes.
......
<!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>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册