<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>HTML5+Three.js 简单布料/衣服模拟效果动画</title>
<style>
body {
font-family: Monospace;
background-color: #000;
color: #000;
margin: 0px;
overflow: hidden;
}
#info {
position: absolute;
padding: 10px;
width: 100%;
text-align: center;
}
a {
text-decoration: underline;
cursor: pointer;
}
</style>
</head>
<body>
<div id="info">Simple Cloth Simulation<br/>
Verlet integration with Constrains relaxation<br/>
<a onclick="wind = !wind;">Wind</a> |
<a onclick="sphere.visible = !sphere.visible;">Ball</a> |
<a onclick="togglePins();">Pins</a>
</div>
<script src="js/three.js"></script>
<script src="js/Detector.js"></script>
<script src="js/OrbitControls.js"></script>
<script src="js/stats.min.js"></script>
<script type="x-shader/x-fragment" id="fragmentShaderDepth">
#include <packing>
uniform sampler2D texture;
varying vec2 vUV;
void main() {
vec4 pixel = texture2D( texture, vUV );
if ( pixel.a < 0.5 ) discard;
gl_FragData[ 0 ] = packDepthToRGBA( 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>
/*
* Cloth Simulation using a relaxed constrains solver
*/
var DAMPING = 0.03;
var DRAG = 1 - DAMPING;
var MASS = 0.1;
var restDistance = 25;
var xSegs = 10;
var ySegs = 10;
var clothFunction = plane(restDistance * xSegs, restDistance * ySegs);
var cloth = new Cloth(xSegs, ySegs);
var GRAVITY = 981 * 1.4;
var gravity = new THREE.Vector3(0, -GRAVITY, 0).multiplyScalar(MASS);
var TIMESTEP = 18 / 1000;
var TIMESTEP_SQ = TIMESTEP * TIMESTEP;
var pins = [];
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();
var lastTime;
function plane(width, height) {
return function(u, v) {
var x = (u - 0.5) * width;
var y = (v + 0.5) * height;
var z = 0;
return new THREE.Vector3(x, y, z);
};
}
function Particle(x, y, z, mass) {
this.position = clothFunction(x, y); // position
this.previous = clothFunction(x, y); // previous
this.original = clothFunction(x, y);
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.add(
this.tmp2.copy(force).multiplyScalar(this.invMass)
);
};
// Performs verlet integration
Particle.prototype.integrate = function(timesq) {
var newPos = this.tmp.subVectors(this.position, this.previous);
newPos.multiplyScalar(DRAG).add(this.position);
newPos.add(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.subVectors(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.add(correctionHalf);
p2.position.sub(correctionHalf);
}
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, v / h, 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
// ]);
// }
// }
this.particles = particles;
this.constrains = constrains;
function index(u, v) {
return u + v * (w + 1);
}
this.index = index;
}
function simulate(time) {
if (!lastTime) {
lastTime = time;
return;
}
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.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() / 600) * 90; //+ 40;
ballPosition.x = Math.cos(Date.now() / 400) * 70;
if (sphere.visible) {
for (particles = cloth.particles, i = 0, il = particles.length; i < il; i++) {
particle = particles[i];
pos = particle.position;
diff.subVectors(pos, ballPosition);
if (diff.length() < ballSize) {
// collided
diff.normalize().multiplyScalar(ballSize);
pos.copy(ballPosition).add(diff);
}
}
}
// Floor Constains
for (particles = cloth.particles, i = 0, il = particles.length; i < il; i++) {
particle = particles[i];
pos = particle.position;
if (pos.y < -250) {
pos.y = -250;
}
}
// Pin Constrains
for (i = 0, il = pins.length; i < il; i++) {
var xy = pins[i];
var p = particles[xy];
p.position.copy(p.original)