three.jswebglcannon.jsammo.jscloth-simulation

Cloth Simulaltion of a 3d obj cloth model


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

2d cloth simuation

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


Solution

  • 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)