I have seen many cloth simulations in three.js . I found it is done only with 2d plane surfaces . But is there a way that I can simulate a 3d cloth model like the one below ..
There are many tutorials for plane 2d simualtion like
And the code for them is given below...
<!DOCTYPE html>
<html lang="en">
<head>
<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>
<script src="../build/three.js"></script>
<script src="../src/OrbitControls.js"></script>
<script>
var params = {
enableWind: true,
tooglePins: togglePins
};
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 windForce = new THREE.Vector3(0, 0, 0);
var tmpForce = new THREE.Vector3();
var lastTime;
function plane(width, height) {
return function(u, v, target) {
var x = (u - 0.5) * width;
var y = (v + 0.5) * height;
var z = 0;
target.set(x, y, z);
};
}
function Particle(x, y, z, mass) {
this.position = new THREE.Vector3();
this.previous = new THREE.Vector3();
this.original = new THREE.Vector3();
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();
// init
clothFunction(x, y, this.position); // position
clothFunction(x, y, this.previous); // previous
clothFunction(x, y, this.original);
}
// 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 satisfyConstraints(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 constraints = [];
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++) {
constraints.push([
particles[index(u, v)],
particles[index(u, v + 1)],
restDistance
]);
constraints.push([
particles[index(u, v)],
particles[index(u + 1, v)],
restDistance
]);
}
}
for (u = w, v = 0; v < h; v++) {
constraints.push([
particles[index(u, v)],
particles[index(u, v + 1)],
restDistance
]);
}
for (v = h, u = 0; u < w; u++) {
constraints.push([
particles[index(u, v)],
particles[index(u + 1, v)],
restDistance
]);
}
this.particles = particles;
this.constraints = constraints;
function index(u, v) {
return u + v * (w + 1);
}
this.index = index;
}
function simulate(time) {
if (!lastTime) {
lastTime = time;
return;
}
var i, j, il, particles, particle, constraints, constraint;
// Aerodynamics forces
if (params.enableWind) {
var indx;
var normal = new THREE.Vector3();
var indices = clothGeometry.index;
var normals = clothGeometry.attributes.normal;
particles = cloth.particles;
for (i = 0, il = indices.count; i < il; i += 3) {
for (j = 0; j < 3; j++) {
indx = indices.getX(i + j);
normal.fromBufferAttribute(normals, indx);
tmpForce.copy(normal).normalize().multiplyScalar(normal.dot(windForce));
particles[indx].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 Constraints
constraints = cloth.constraints;
il = constraints.length;
for (i = 0; i < il; i++) {
constraint = constraints[i];
satisfyConstraints(constraint[0], constraint[1], constraint[2]);
}
// Floor Constraints
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 Constraints
for (i = 0, il = pins.length; i < il; i++) {
var xy = pins[i];
var p = particles[xy];
p.position.copy(p.original);
p.previous.copy(p.original);
}
}
/* testing cloth simulation */
var pinsFormation = [];
var pins = [6];
pinsFormation.push(pins);
pins = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
pinsFormation.push(pins);
pins = [0];
pinsFormation.push(pins);
pins = []; // cut the rope ;)
pinsFormation.push(pins);
pins = [0, cloth.w]; // classic 2 pins
pinsFormation.push(pins);
pins = pinsFormation[1];
function togglePins() {
pins = pinsFormation[~~(Math.random() * pinsFormation.length)];
}
var container, stats;
var camera, scene, renderer;
var clothGeometry;
var sphere;
var object;
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
// scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
scene.fog = new THREE.Fog(0xcce0ff, 500, 10000);
// camera
camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(1000, 50, 1500);
// lights
scene.add(new THREE.AmbientLight(0x666666));
var light = new THREE.DirectionalLight(0xdfebff, 1);
light.position.set(50, 200, 100);
light.position.multiplyScalar(1.3);
light.castShadow = true;
light.shadow.mapSize.width = 1024;
light.shadow.mapSize.height = 1024;
var d = 300;
light.shadow.camera.left = -d;
light.shadow.camera.right = d;
light.shadow.camera.top = d;
light.shadow.camera.bottom = -d;
light.shadow.camera.far = 1000;
scene.add(light);
// cloth material
var loader = new THREE.TextureLoader();
var clothTexture = loader.load('textures/water/Water_1_M_Flow.jpg');
clothTexture.anisotropy = 16;
var clothMaterial = new THREE.MeshLambertMaterial({
map: clothTexture,
side: THREE.DoubleSide,
// wireframe: true,
// alphaTest: 0.5
});
// cloth geometry
clothGeometry = new THREE.ParametricBufferGeometry(clothFunction, cloth.w, cloth.h);
// cloth mesh
object = new THREE.Mesh(clothGeometry, clothMaterial);
object.position.set(0, 0, 0);
object.castShadow = true;
scene.add(object);
// object.customDepthMaterial = new THREE.MeshDepthMaterial({
// depthPacking: THREE.RGBADepthPacking,
// map: clothTexture,
// alphaTest: 0.5
// });
// renderer
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.shadowMap.enabled = true;
// controls
var controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.maxPolarAngle = Math.PI * 0.5;
controls.minDistance = 1000;
controls.maxDistance = 5000;
window.addEventListener('resize', onWindowResize, false);
}
//
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
//
function animate() {
requestAnimationFrame(animate);
var time = Date.now();
var windStrength = Math.cos(time / 7000) * 20 + 40;
windForce.set(Math.sin(time / 2000), Math.cos(time / 3000), Math.sin(time / 1000));
windForce.normalize();
windForce.multiplyScalar(windStrength);
simulate(time);
render();
}
function render() {
var p = cloth.particles;
for (var i = 0, il = p.length; i < il; i++) {
var v = p[i].position;
clothGeometry.attributes.position.setXYZ(i, v.x, v.y, v.z);
}
clothGeometry.attributes.position.needsUpdate = true;
clothGeometry.computeVertexNormals();
renderer.render(scene, camera);
}
</script>
</body>
</html>
can I make a mesh Just like hanging the cloth and when wind blows they must react accordingly. Whether by using three.js along with ammo.js or cannon.js too
The code you posted will not do clothing as it as no collisions. The code in ammo.js will but you need to generate the clothing yourself.
Cloth is typically simulated using masses and springs
M--s--M--s--M--s--M
|\ /|\ /|\ /|
| \ / | \ / | \ / |
s s s s s s s
| / \ | / \ | / \ |
|/ \|/ \|/ \|
M--s--M--s--M--s--M
|\ /|\ /|\ /|
| \ / | \ / | \ / |
s s s s s s s
| / \ | / \ | / \ |
|/ \|/ \|/ \|
M--s--M--s--M--s--M
above is a diagram of masses (M) and springs (s). Each spring is connected between 2 masses and tries to keep the masses from stretching too far apart and getting too close as well. You need 1000s of masses and springs to simulate clothing.
The reason the demos are in planes is because it's the easiest demo to make. If you want clothes you need to walk the polygons of your clothing and then generate masses and springs. Further, you need to associate the masses with their corresponding vertices in the clothing model so that after the simulation runs you can apply the masses' new positions back to the vertices of the clothing.
On top of that you need collisions on the body of the character you're going to put the clothing on that the masses will collide with so they don't go inside the body and just fall ot the floor. Most physics engines have a few primitives like box, sphere, capsule, cylinder that are optimzied. They can also use generic polygons for collision but they are slower so it's up to you to decide if you can use a few of the primitive shapes attached to your model to do the collisions or if you need the higher fidelity of using polygons for your collisions.
In either case the more masses you add per area of cloth the better the cloth will look but the slower it will run so you have to decide where the trade off is between looking good and running fast.
ammo.js AFAICT is undocumented except to say it is a port of Bullet Physics who's documentation is here
I don't see any JavaScript demos for custom cloth.
This ammo.js demo doesn't seem to fit clothing because if those shapes shown were actually clothing they'd just collapse into a pile, not act like they are inflated but then maybe getting that behavior is a setting. You'll need to dig through the docs and/or that sample
You'll need to separate your clothing geometry from the human body / manekin model, turn the clothing into a soft body or manually generate the masses and springs and then also make human/manekin hard body either from a mesh or from primitives so it holds up the clothing.
If I was doing it I'd start with a hard body cube inside a soft body sphere and see how detailed I need to make the sphere for it to behave like clothing (fold and crease)