ソースを参照

softbody ammo start

Guide 6 年 前
コミット
9a7e3e8117

+ 223 - 0
src/Meshes/mesh.ts

@@ -2403,6 +2403,229 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
         return this;
     }
 
+    /**
+     * Increase the number of facets and hence vertices in a mesh
+     * Introduced for use with Ammo.JSPlugin soft bodies
+     * Warning : the mesh is really modified even if not set originally as updatable. A new VertexBuffer is created under the hood each call.
+     * @param numberPerEdge the number of new vertices to add to each edge of a facet, optional default 1
+     */
+    public increaseVertices(numberPerEdge: number): void {
+        var vertex_data = VertexData.ExtractFromMesh(this);
+        var uvs = vertex_data.uvs;
+        var currentIndices = vertex_data.indices;
+        var positions = vertex_data.positions;
+        var normals = vertex_data.normals;
+
+        if (currentIndices === null || positions === null || normals === null || uvs === null) {
+            Logger.Warn("VertexData contains null entries");
+        }
+        else {
+            var segments: number = numberPerEdge + 1; //segments per current facet edge, become sides of new facets
+            var tempIndices: Array<Array<number>> = new Array();
+            for (var i = 0; i < segments + 1; i++) {
+                tempIndices[i] = new Array();
+            }
+            var a: number;  //vertex index of one end of a side
+            var b: number; //vertex index of other end of the side
+            var deltaPosition: Vector3 = new Vector3(0, 0, 0);
+            var deltaNormal: Vector3 = new Vector3(0, 0, 0);
+            var deltaUV: Vector2 = new Vector2(0, 0);
+            var indices: number[] = new Array();
+            var vertexIndex: number[] = new Array();
+            var side: Array<Array<Array<number>>> = new Array();
+            var len: number;
+            var positionPtr: number = positions.length;
+            var uvPtr: number = uvs.length;
+
+            for (var i = 0; i < currentIndices.length; i += 3) {
+                vertexIndex[0] = currentIndices[i];
+                vertexIndex[1] = currentIndices[i + 1];
+                vertexIndex[2] = currentIndices[i + 2];
+                for (var j = 0; j < 3; j++) {
+                    a = vertexIndex[j];
+                    b = vertexIndex[(j + 1) % 3];
+                    if (side[a] === undefined  && side[b] ===  undefined) {
+                        side[a] = new Array();
+                        side[b] = new Array();
+                    }
+                    else {
+                        if (side[a] === undefined) {
+                            side[a] = new Array();
+                        }
+                        if (side[b] === undefined) {
+                            side[b] = new Array();
+                        }
+                    }
+                    if (side[a][b]  === undefined  && side[b][a] === undefined) {
+                        side[a][b] = [];
+                        deltaPosition.x = (positions[3 * b] - positions[3 * a]) / segments;
+                        deltaPosition.y = (positions[3 * b + 1] - positions[3 * a + 1]) / segments;
+                        deltaPosition.z = (positions[3 * b + 2] - positions[3 * a + 2]) / segments;
+                        deltaNormal.x = (normals[3 * b] - normals[3 * a]) / segments;
+                        deltaNormal.y = (normals[3 * b + 1] - normals[3 * a + 1]) / segments;
+                        deltaNormal.z = (normals[3 * b + 2] - normals[3 * a + 2]) / segments;
+                        deltaUV.x = (uvs[2 * b] - uvs[2 * a]) / segments;
+                        deltaUV.y = (uvs[2 * b + 1] - uvs[2 * a + 1]) / segments;
+                        side[a][b].push(a);
+                        for (var k = 1; k < segments; k++) {
+                            side[a][b].push(positions.length / 3);
+                            positions[positionPtr] = positions[3 * a] + k * deltaPosition.x;
+                            normals[positionPtr++] = normals[3 * a] + k * deltaNormal.x;
+                            positions[positionPtr] = positions[3 * a + 1] + k * deltaPosition.y;
+                            normals[positionPtr++] = normals[3 * a + 1] + k * deltaNormal.y;
+                            positions[positionPtr] = positions[3 * a + 2] + k * deltaPosition.z;
+                            normals[positionPtr++] = normals[3 * a + 2] + k * deltaNormal.z;
+                            uvs[uvPtr++] = uvs[2 * a] + k * deltaUV.x;
+                            uvs[uvPtr++] = uvs[2 * a + 1] + k * deltaUV.y;
+                        }
+                        side[a][b].push(b);
+                        side[b][a] = new Array();
+                        len = side[a][b].length;
+                        for (var idx = 0; idx < len; idx++) {
+                            side[b][a][idx] = side[a][b][len - 1 - idx];
+                        }
+                    }
+                    else {
+                        if (side[a][b] === undefined) {
+                            side[a][b] = new Array();
+                            len = side[b][a].length;
+                            for (var idx = 0; idx < len; idx++) {
+                                side[a][b][idx] = side[b][a][len - 1 - idx];
+                            }
+                        }
+                        if (side[b][a] === undefined) {
+                            side[b][a] = new Array();
+                            len = side[a][b].length;
+                            for (var idx = 0; idx < len; idx++) {
+                                side[b][a][idx] = side[a][b][len - 1 - idx];
+                            }
+                        }
+                    }
+                }
+                //Calculate positions, normals and uvs of new internal vertices
+                tempIndices[0][0] = currentIndices[i];
+                tempIndices[1][0] = side[currentIndices[i]][currentIndices[i + 1]][1];
+                tempIndices[1][1] = side[currentIndices[i]][currentIndices[i + 2]][1];
+                for (var k = 2; k < segments; k++) {
+                    tempIndices[k][0] = side[currentIndices[i]][currentIndices[i + 1]][k];
+                    tempIndices[k][k] = side[currentIndices[i]][currentIndices[i + 2]][k];
+                    deltaPosition.x = (positions[3 * tempIndices[k][k]] - positions[3 * tempIndices[k][0]]) / k;
+                    deltaPosition.y = (positions[3 * tempIndices[k][k] + 1] - positions[3 * tempIndices[k][0] + 1]) / k;
+                    deltaPosition.z = (positions[3 * tempIndices[k][k] + 2] - positions[3 * tempIndices[k][0] + 2]) / k;
+                    deltaNormal.x = (normals[3 * tempIndices[k][k]] - normals[3 * tempIndices[k][0]]) / k;
+                    deltaNormal.y = (normals[3 * tempIndices[k][k] + 1] - normals[3 * tempIndices[k][0] + 1]) / k;
+                    deltaNormal.z = (normals[3 * tempIndices[k][k] + 2] - normals[3 * tempIndices[k][0] + 2]) / k;
+                    deltaUV.x = (uvs[2 * tempIndices[k][k]] - uvs[2 * tempIndices[k][0]]) / k;
+                    deltaUV.y = (uvs[2 * tempIndices[k][k] + 1] - uvs[2 * tempIndices[k][0] + 1]) / k;
+                    for (var j = 1; j < k; j++) {
+                        tempIndices[k][j] = positions.length / 3;
+                        positions[positionPtr] = positions[3 * tempIndices[k][0]] + j * deltaPosition.x;
+                        normals[positionPtr++] = normals[3 * tempIndices[k][0]] + j * deltaNormal.x;
+                        positions[positionPtr] = positions[3 * tempIndices[k][0] + 1] + j * deltaPosition.y;
+                        normals[positionPtr++] = normals[3 * tempIndices[k][0] + 1] + j * deltaNormal.y;
+                        positions[positionPtr] = positions[3 * tempIndices[k][0] + 2] + j * deltaPosition.z;
+                        normals[positionPtr++] = normals[3 * tempIndices[k][0] + 2] + j * deltaNormal.z;
+                        uvs[uvPtr++] = uvs[2 * tempIndices[k][0]] + j * deltaUV.x;
+                        uvs[uvPtr++] = uvs[2 * tempIndices[k][0] + 1] + j * deltaUV.y;
+                    }
+                }
+                tempIndices[segments] = side[currentIndices[i + 1]][currentIndices[i + 2]];
+
+                // reform indices
+                indices.push(tempIndices[0][0], tempIndices[1][0], tempIndices[1][1]);
+                for (var k = 1; k < segments; k++) {
+                    for (var j = 0; j < k; j++) {
+                        indices.push(tempIndices[k][j], tempIndices[k + 1][j], tempIndices[k + 1][j + 1]);
+                        indices.push(tempIndices[k][j], tempIndices[k + 1][j + 1], tempIndices[k][j + 1]);
+                    }
+                    indices.push(tempIndices[k][j], tempIndices[k + 1][j], tempIndices[k + 1][j + 1]);
+                }
+            }
+
+            vertex_data.indices = indices;
+            vertex_data.applyToMesh(this);
+        }
+    }
+
+    /**
+     * Force adjacent facets to share vertices and remove any facets that have all vertices in a line
+     * This will undo any application of covertToFlatShadedMesh
+     * Introduced for use with Ammo.JSPlugin soft bodies
+     * Warning : the mesh is really modified even if not set originally as updatable. A new VertexBuffer is created under the hood each call.
+     */
+    public forceSharedVertices(): void {
+        var vertex_data = VertexData.ExtractFromMesh(this);
+        var currentUVs = vertex_data.uvs;
+        var currentIndices = vertex_data.indices;
+        var currentPositions = vertex_data.positions;
+        var currentNormals = vertex_data.normals;
+
+        if (currentIndices === null || currentPositions === null || currentNormals === null || currentUVs === null) {
+            Logger.Warn("VertexData contains null entries");
+        }
+        else {
+            var positions: Array<number> = new Array();
+            var indices: Array<number> = new Array();
+            var uvs: Array<number> = new Array();
+            var pstring: Array<string> = new Array(); //lists facet vertex positions (a,b,c) as string "a|b|c"
+
+            var indexPtr: number = 0; // pointer to next available index value
+            var uniquePositions: Array<string> = new Array(); // unique vertex positions
+            var ptr: number; // pointer to element in uniquePositions
+            var facet: Array<number>;
+
+            for (var i = 0; i < currentIndices.length; i += 3) {
+                facet = [currentIndices[i], currentIndices[i + 1], currentIndices[i + 2]]; //facet vertex indices
+                pstring = new Array();
+                for (var j = 0; j < 3; j++) {
+                    pstring[j] = "";
+                    for (var k = 0; k < 3; k++) {
+                        //small values make 0
+                        if (Math.abs(currentPositions[3 * facet[j] + k]) < 0.00000001) {
+                            currentPositions[3 * facet[j] + k] = 0;
+                        }
+                        pstring[j] += currentPositions[3 * facet[j] + k] + "|";
+                    }
+                    pstring[j] = pstring[j].slice(0, -1);
+                }
+                //check facet vertices to see that none are repeated
+                // do not process any facet that has a repeated vertex, ie is a line
+                if (!(pstring[0] == pstring[1] || pstring[0] == pstring[2] || pstring[1] == pstring[2])) {
+                    //for each facet position check if already listed in uniquePositions
+                    // if not listed add to uniquePositions and set index pointer
+                    // if listed use its index in uniquePositions and new index pointer
+                    for (var j = 0; j < 3; j++) {
+                        ptr = uniquePositions.indexOf(pstring[j]);
+                        if (ptr < 0) {
+                            uniquePositions.push(pstring[j]);
+                            ptr = indexPtr++;
+                            //not listed so add individual x, y, z coordinates to positions
+                            for (var k = 0; k < 3; k++) {
+                                positions.push(currentPositions[3 * facet[j] + k]);
+                            }
+                            for (var k = 0; k < 2; k++) {
+                                uvs.push(currentUVs[2 * facet[j] + k]);
+                            }
+                        }
+                        // add new index pointer to indices array
+                        indices.push(ptr);
+                    }
+                }
+            }
+
+            var normals: Array<number> = new Array();
+            VertexData.ComputeNormals(positions, indices, normals);
+
+            //create new vertex data object and update
+            vertex_data.positions = positions;
+            vertex_data.indices = indices;
+            vertex_data.normals = normals;
+            vertex_data.uvs = uvs;
+
+            vertex_data.applyToMesh(this);
+        }
+    }
+
     // Instances
     /** @hidden */
     public static _instancedMeshFactory(name: string, mesh: Mesh): InstancedMesh {

+ 8 - 0
src/Physics/IPhysicsEngine.ts

@@ -42,6 +42,14 @@ export interface IPhysicsEnginePlugin {
     setBodyFriction(impostor: PhysicsImpostor, friction: number): void;
     getBodyRestitution(impostor: PhysicsImpostor): number;
     setBodyRestitution(impostor: PhysicsImpostor, restitution: number): void;
+    getBodyPressure?(impostor: PhysicsImpostor): number;
+    setBodyPressure?(impostor: PhysicsImpostor, pressure: number): void;
+    getBodyStiffness?(impostor: PhysicsImpostor): number;
+    setBodyStiffness?(impostor: PhysicsImpostor, stiffness: number): void;
+    getBodyVelocityIterations?(impostor: PhysicsImpostor): number;
+    setBodyVelocityIterations?(impostor: PhysicsImpostor, velocityIterations: number): void;
+    getBodyPositionIterations?(impostor: PhysicsImpostor): number;
+    setBodyPositionIterations?(impostor: PhysicsImpostor, positionIterations: number): void;
     sleepBody(impostor: PhysicsImpostor): void;
     wakeUpBody(impostor: PhysicsImpostor): void;
     //Joint Update

+ 402 - 67
src/Physics/Plugins/ammoJSPlugin.ts

@@ -4,8 +4,10 @@ import { Logger } from "../../Misc/logger";
 import { PhysicsImpostor, IPhysicsEnabledObject } from "../../Physics/physicsImpostor";
 import { PhysicsJoint, IMotorEnabledJoint, DistanceJointData } from "../../Physics/physicsJoint";
 import { VertexBuffer } from "../../Meshes/buffer";
+import { VertexData } from "../../Meshes/mesh.vertexData";
 import { Nullable } from "../../types";
 import { AbstractMesh } from "../../Meshes/abstractMesh";
+import { Mesh } from "../../Meshes/mesh";
 
 declare var Ammo: any;
 
@@ -39,6 +41,7 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
     private _dispatcher: any;
     private _overlappingPairCache: any;
     private _solver: any;
+    private _softBodySolver: any;
     private _tmpAmmoVectorA: any;
     private _tmpAmmoVectorB: any;
     private _tmpAmmoVectorC: any;
@@ -50,10 +53,11 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
 
     /**
      * Initializes the ammoJS plugin
+     * @param _includeSoftBodies when true you can mix tigid and soft bodies (default false)
      * @param _useDeltaForWorldStep if the time between frames should be used when calculating physics steps (Default: true)
      * @param ammoInjection can be used to inject your own ammo reference
      */
-    public constructor(private _useDeltaForWorldStep: boolean = true, ammoInjection: any = Ammo) {
+    public constructor(private _includeSoftBodies: boolean = false, private _useDeltaForWorldStep: boolean = true, ammoInjection: any = Ammo) {
         if (typeof ammoInjection === "function") {
             ammoInjection(this.bjsAMMO);
         }else {
@@ -66,11 +70,22 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
         }
 
         // Initialize the physics world
-        this._collisionConfiguration = new this.bjsAMMO.btDefaultCollisionConfiguration();
-        this._dispatcher = new this.bjsAMMO.btCollisionDispatcher(this._collisionConfiguration);
-        this._overlappingPairCache = new this.bjsAMMO.btDbvtBroadphase();
-        this._solver = new this.bjsAMMO.btSequentialImpulseConstraintSolver();
-        this.world = new this.bjsAMMO.btDiscreteDynamicsWorld(this._dispatcher, this._overlappingPairCache, this._solver, this._collisionConfiguration);
+        if (this._includeSoftBodies) {
+            this._collisionConfiguration = new this.bjsAMMO.btSoftBodyRigidBodyCollisionConfiguration();
+            this._dispatcher = new this.bjsAMMO.btCollisionDispatcher(this._collisionConfiguration);
+            this._overlappingPairCache = new this.bjsAMMO.btDbvtBroadphase();
+            this._solver = new this.bjsAMMO.btSequentialImpulseConstraintSolver();
+            this._softBodySolver = new this.bjsAMMO.btDefaultSoftBodySolver();
+            this.world = new this.bjsAMMO.btSoftRigidDynamicsWorld(this._dispatcher, this._overlappingPairCache, this._solver, this._collisionConfiguration, this._softBodySolver);
+        }
+        else {
+            this._collisionConfiguration = new this.bjsAMMO.btDefaultCollisionConfiguration();
+            this._dispatcher = new this.bjsAMMO.btCollisionDispatcher(this._collisionConfiguration);
+            this._overlappingPairCache = new this.bjsAMMO.btDbvtBroadphase();
+            this._solver = new this.bjsAMMO.btSequentialImpulseConstraintSolver();
+            this.world = new this.bjsAMMO.btDiscreteDynamicsWorld(this._dispatcher, this._overlappingPairCache, this._solver, this._collisionConfiguration);
+        }
+
         this._tmpAmmoConcreteContactResultCallback = new this.bjsAMMO.ConcreteContactResultCallback();
         this._tmpAmmoConcreteContactResultCallback.addSingleResult = () => { this._tmpContactCallbackResult = true; };
 
@@ -90,6 +105,9 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
     public setGravity(gravity: Vector3): void {
         this._tmpAmmoVectorA.setValue(gravity.x, gravity.y, gravity.z);
         this.world.setGravity(this._tmpAmmoVectorA);
+        if (this._includeSoftBodies) {
+            this.world.getWorldInfo().set_m_gravity(this._tmpAmmoVectorA);
+        }
     }
 
     /**
@@ -172,14 +190,21 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
     public executeStep(delta: number, impostors: Array<PhysicsImpostor>): void {
         for (var impostor of impostors) {
             // Update physics world objects to match babylon world
-            impostor.beforeStep();
+            if (!impostor.soft) {
+                impostor.beforeStep();
+            }
         }
 
         this._stepSimulation(this._useDeltaForWorldStep ? delta : this._timeStep, this._maxSteps, this._fixedTimeStep);
 
         for (var mainImpostor of impostors) {
             // After physics update make babylon world objects match physics world objects
-            mainImpostor.afterStep();
+            if (mainImpostor.soft) {
+                this.afterSoftStep(mainImpostor);
+            }
+            else {
+                mainImpostor.afterStep();
+            }
 
             // Handle collision event
             if (mainImpostor._onPhysicsCollideCallbacks.length > 0) {
@@ -199,30 +224,89 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
         }
     }
 
+    /**
+     * Update babylon mesh vertices vertices to match physics world object
+     * @param impostor imposter to apply impulse
+     */
+    public afterSoftStep(impostor: PhysicsImpostor): void {
+        var object = impostor.object;
+        var vertexPositions = object.getVerticesData(VertexBuffer.PositionKind);
+        if (!vertexPositions) {
+            vertexPositions = [];
+        }
+        var vertexNormals = object.getVerticesData(VertexBuffer.NormalKind);
+        if (!vertexNormals) {
+            vertexNormals = [];
+        }
+
+        var nbVertices = vertexPositions.length / 3;
+        var bodyVertices = impostor.physicsBody.get_m_nodes();
+        var node: any;
+        var nodePositions: any;
+        var nodeNormals: any;
+        var x, y, z: number;
+        var nx, ny, nz: number;
+        for (var n = 0; n < nbVertices; n++) {
+            node = bodyVertices.at(n);
+            nodePositions = node.get_m_x();
+            x = nodePositions.x();
+            y = nodePositions.y();
+            z = -nodePositions.z();
+            var nodeNormals = node.get_m_n();
+            nx = nodeNormals.x();
+            ny = nodeNormals.y();
+            nz = -nodeNormals.z();
+
+            vertexPositions[3 * n] = x;
+            vertexPositions[3 * n + 1] = y;
+            vertexPositions[3 * n + 2] = z;
+            vertexNormals[3 * n] = nx;
+            vertexNormals[3 * n + 1] = ny;
+            vertexNormals[3 * n + 2] = nz;
+        }
+
+        var vertex_data = new VertexData();
+
+        vertex_data.positions = vertexPositions;
+        vertex_data.normals = vertexNormals;
+        vertex_data.uvs = object.getVerticesData(VertexBuffer.UVKind);
+        vertex_data.colors = object.getVerticesData(VertexBuffer.ColorKind);
+        if (object && object.getIndices) {
+            vertex_data.indices = object.getIndices();
+        }
+
+        vertex_data.applyToMesh(<Mesh>object);
+    }
+
     private _tmpVector = new Vector3();
     private _tmpMatrix = new Matrix();
     /**
-     * Applies an implulse on the imposter
+     * Applies an impulse on the imposter
      * @param impostor imposter to apply impulse
      * @param force amount of force to be applied to the imposter
      * @param contactPoint the location to apply the impulse on the imposter
      */
     public applyImpulse(impostor: PhysicsImpostor, force: Vector3, contactPoint: Vector3) {
-        impostor.physicsBody.activate();
-        var worldPoint = this._tmpAmmoVectorA;
-        var impulse = this._tmpAmmoVectorB;
-
-        // Convert contactPoint into world space
-        if (impostor.object && impostor.object.getWorldMatrix) {
-            impostor.object.getWorldMatrix().invertToRef(this._tmpMatrix);
-            Vector3.TransformCoordinatesToRef(contactPoint, this._tmpMatrix, this._tmpVector);
-            contactPoint = this._tmpVector;
-        }
+        if (!impulse.soft) {
+            impostor.physicsBody.activate();
+            var worldPoint = this._tmpAmmoVectorA;
+            var impulse = this._tmpAmmoVectorB;
+
+            // Convert contactPoint into world space
+            if (impostor.object && impostor.object.getWorldMatrix) {
+                impostor.object.getWorldMatrix().invertToRef(this._tmpMatrix);
+                Vector3.TransformCoordinatesToRef(contactPoint, this._tmpMatrix, this._tmpVector);
+                contactPoint = this._tmpVector;
+            }
 
-        worldPoint.setValue(contactPoint.x, contactPoint.y, contactPoint.z);
-        impulse.setValue(force.x, force.y, force.z);
+            worldPoint.setValue(contactPoint.x, contactPoint.y, contactPoint.z);
+            impulse.setValue(force.x, force.y, force.z);
 
-        impostor.physicsBody.applyImpulse(impulse, worldPoint);
+            impostor.physicsBody.applyImpulse(impulse, worldPoint);
+        }
+        else {
+            Logger.Warn("Cannot be applied to a soft body");
+        }
     }
 
     /**
@@ -232,21 +316,26 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
      * @param contactPoint the location to apply the force on the imposter
      */
     public applyForce(impostor: PhysicsImpostor, force: Vector3, contactPoint: Vector3) {
-        impostor.physicsBody.activate();
-        var worldPoint = this._tmpAmmoVectorA;
-        var impulse = this._tmpAmmoVectorB;
-
-        // Convert contactPoint into world space
-        if (impostor.object && impostor.object.getWorldMatrix) {
-            impostor.object.getWorldMatrix().invertToRef(this._tmpMatrix);
-            Vector3.TransformCoordinatesToRef(contactPoint, this._tmpMatrix, this._tmpVector);
-            contactPoint = this._tmpVector;
-        }
+        if (!impostor.soft) {
+            impostor.physicsBody.activate();
+            var worldPoint = this._tmpAmmoVectorA;
+            var impulse = this._tmpAmmoVectorB;
+
+            // Convert contactPoint into world space
+            if (impostor.object && impostor.object.getWorldMatrix) {
+                impostor.object.getWorldMatrix().invertToRef(this._tmpMatrix);
+                Vector3.TransformCoordinatesToRef(contactPoint, this._tmpMatrix, this._tmpVector);
+                contactPoint = this._tmpVector;
+            }
 
-        worldPoint.setValue(contactPoint.x, contactPoint.y, contactPoint.z);
-        impulse.setValue(force.x, force.y, force.z);
+            worldPoint.setValue(contactPoint.x, contactPoint.y, contactPoint.z);
+            impulse.setValue(force.x, force.y, force.z);
 
-        impostor.physicsBody.applyForce(impulse, worldPoint);
+            impostor.physicsBody.applyForce(impulse, worldPoint);
+        }
+        else {
+            Logger.Warn("Cannot be applied to a soft body");
+        }
     }
 
     /**
@@ -269,37 +358,51 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
             var colShape = this._createShape(impostor);
             var mass = impostor.getParam("mass");
             impostor._pluginData.mass = mass;
-            var localInertia = new Ammo.btVector3(0, 0, 0);
-            var startTransform = new Ammo.btTransform();
-            startTransform.setIdentity();
-            if (mass !== 0) {
-                colShape.calculateLocalInertia(mass, localInertia);
-            }
-            this._tmpAmmoVectorA.setValue(impostor.object.position.x, impostor.object.position.y, impostor.object.position.z);
-            this._tmpAmmoQuaternion.setValue(impostor.object.rotationQuaternion!.x, impostor.object.rotationQuaternion!.y, impostor.object.rotationQuaternion!.z, impostor.object.rotationQuaternion!.w);
-            startTransform.setOrigin(this._tmpAmmoVectorA);
-            startTransform.setRotation(this._tmpAmmoQuaternion);
-            var myMotionState = new Ammo.btDefaultMotionState(startTransform);
-            var rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, myMotionState, colShape, localInertia);
-            var body = new Ammo.btRigidBody(rbInfo);
-
-            // Make objects kinematic if it's mass is 0
-            if (mass === 0) {
-                body.setCollisionFlags(body.getCollisionFlags() | AmmoJSPlugin.KINEMATIC_FLAG);
-                body.setActivationState(AmmoJSPlugin.DISABLE_DEACTIVATION_FLAG);
+            if (impostor.soft) {
+                Logger.Warn("Stiffness ");
+                Ammo.castObject(colShape, Ammo.btCollisionObject).getCollisionShape().setMargin(0.05);
+
+                this.world.addSoftBody(colShape, 1, -1);
+                impostor.physicsBody = colShape;
+                impostor._pluginData.toDispose.concat([colShape]);
+                this.setBodyPressure(impostor, impostor.getParam("pressure"));
+                this.setBodyStiffness(impostor, impostor.getParam("stiffness"));
+                this.setBodyVelocityIterations(impostor, impostor.getParam("velocityIterations"));
+                this.setBodyPositionIterations(impostor, impostor.getParam("positionIterations"));
             }
+            else {
+                Logger.Warn("mass " + impostor.mass);
+                var localInertia = new Ammo.btVector3(0, 0, 0);
+                var startTransform = new Ammo.btTransform();
+                startTransform.setIdentity();
+                if (mass !== 0) {
+                    colShape.calculateLocalInertia(mass, localInertia);
+                }
+                this._tmpAmmoVectorA.setValue(impostor.object.position.x, impostor.object.position.y, impostor.object.position.z);
+                this._tmpAmmoQuaternion.setValue(impostor.object.rotationQuaternion!.x, impostor.object.rotationQuaternion!.y, impostor.object.rotationQuaternion!.z, impostor.object.rotationQuaternion!.w);
+                startTransform.setOrigin(this._tmpAmmoVectorA);
+                startTransform.setRotation(this._tmpAmmoQuaternion);
+                var myMotionState = new Ammo.btDefaultMotionState(startTransform);
+                var rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, myMotionState, colShape, localInertia);
+                var body = new Ammo.btRigidBody(rbInfo);
+
+                // Make objects kinematic if it's mass is 0
+                if (mass === 0) {
+                    body.setCollisionFlags(body.getCollisionFlags() | AmmoJSPlugin.KINEMATIC_FLAG);
+                    body.setActivationState(AmmoJSPlugin.DISABLE_DEACTIVATION_FLAG);
+                }
 
-            // Disable collision if NoImpostor, but keep collision if shape is btCompoundShape
-            if (impostor.type == PhysicsImpostor.NoImpostor && !colShape.getChildShape) {
-                body.setCollisionFlags(body.getCollisionFlags() | AmmoJSPlugin.DISABLE_COLLISION_FLAG);
-            }
+                // Disable collision if NoImpostor, but keep collision if shape is btCompoundShape
+                if (impostor.type == PhysicsImpostor.NoImpostor && !colShape.getChildShape) {
+                    body.setCollisionFlags(body.getCollisionFlags() | AmmoJSPlugin.DISABLE_COLLISION_FLAG);
+                }
 
-            this.world.addRigidBody(body);
-            impostor.physicsBody = body;
+                this.world.addRigidBody(body);
+                impostor.physicsBody = body;
+                impostor._pluginData.toDispose.concat([body, rbInfo, myMotionState, startTransform, localInertia, colShape]);
+            }
             this.setBodyRestitution(impostor, impostor.getParam("restitution"));
             this.setBodyFriction(impostor, impostor.getParam("friction"));
-
-            impostor._pluginData.toDispose.concat([body, rbInfo, myMotionState, startTransform, localInertia, colShape]);
         }
     }
 
@@ -422,6 +525,90 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
         return triangleCount;
     }
 
+    /**
+     * Create an impostor's soft body
+     * Initialise the soft body vertices to match its object's (mesh) vertices
+     * Softbody vertices (nodes) are in world space and to match this
+     * The object's position and rotation is set to zero and so its vertices are also then set in world space
+     * @param impostor to create the softbody for
+     */
+    private _createSoftbody(impostor: PhysicsImpostor) {
+        var object = impostor.object;
+        if (object && object.getIndices && object.getWorldMatrix && object.getChildMeshes) {
+            var indices = object.getIndices();
+            if (!indices) {
+                indices = [];
+            }
+            var vertexPositions = object.getVerticesData(VertexBuffer.PositionKind);
+            if (!vertexPositions) {
+                vertexPositions = [];
+            }
+            var vertexNormals = object.getVerticesData(VertexBuffer.NormalKind);
+            if (!vertexNormals) {
+                vertexNormals = [];
+            }
+            object.computeWorldMatrix(false);
+            var triPoints = [];
+            var triNorms = [];
+            var newPoints = [];
+            var newNorms = [];
+            for (var i = 0; i < vertexPositions.length; i += 3) {
+                    var v = new Vector3(vertexPositions[i], vertexPositions[i + 1], vertexPositions[i + 2]);
+                    var n = new Vector3(vertexNormals[i], vertexNormals[i + 1], vertexNormals[i + 2]);
+                    v = Vector3.TransformCoordinates(v, object.getWorldMatrix());
+                    n = Vector3.TransformNormal(n, object.getWorldMatrix());
+                    triPoints.push(v.x, v.y, -v.z);
+                    triNorms.push(n.x, n.y, -n.z);
+                    newPoints.push(v.x, v.y, v.z);
+                    newNorms.push(n.x, n.y, n.z);
+            }
+
+            var vertex_data = new VertexData();
+
+            vertex_data.positions = newPoints;
+            vertex_data.normals = newNorms;
+            vertex_data.uvs = object.getVerticesData(VertexBuffer.UVKind);
+            vertex_data.colors = object.getVerticesData(VertexBuffer.ColorKind);
+            if (object && object.getIndices) {
+                vertex_data.indices = object.getIndices();
+            }
+
+            vertex_data.applyToMesh(<Mesh>object);
+
+            object.position = Vector3.Zero();
+            object.rotationQuaternion = null;
+            object.rotation = Vector3.Zero();
+            object.computeWorldMatrix(true);
+
+            if (vertexPositions.length === 0) {
+                return new Ammo.btCompoundShape();
+            }
+            else {
+                var softBody = new Ammo.btSoftBodyHelpers().CreateFromTriMesh(
+                    this.world.getWorldInfo(),
+                    triPoints,
+                    object.getIndices(),
+                    indices.length / 3,
+                    true
+                );
+
+                var nbVertices = vertexPositions.length / 3;
+                var bodyVertices = softBody.get_m_nodes();
+                var node: any;
+                var nodeNormals: any;
+                for (var i = 0; i < nbVertices; i++) {
+                    node = bodyVertices.at(i);
+                    var nodeNormals = node.get_m_n();
+                    nodeNormals.setX(triNorms[3 * i]);
+                    nodeNormals.setY(triNorms[3 * i + 1]);
+                    nodeNormals.setZ(-triNorms[3 * i + 2]);
+                }
+                softBody.get_m_cfg().set_collisions(0x11);
+                return softBody;
+            }
+        }
+    }
+
     private _createShape(impostor: PhysicsImpostor, ignoreChildren = false) {
         var object = impostor.object;
 
@@ -500,6 +687,10 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
                 // Fill with sphere but collision is disabled on the rigid body in generatePhysicsBody, using an empty shape caused unexpected movement with joints
                 returnValue = new Ammo.btSphereShape(extendSize.x / 2);
                 break;
+            case PhysicsImpostor.SoftbodyImpostor:
+                // Only usable with a mesh that has sufficient and shared vertices
+                returnValue = this._createSoftbody(impostor);
+                break;
             default:
                 Logger.Warn("The impostor type is not currently supported by the ammo plugin.");
                 break;
@@ -578,8 +769,13 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
      * @param velocity velocity to set
      */
     public setLinearVelocity(impostor: PhysicsImpostor, velocity: Vector3) {
-        this._tmpAmmoVectorA.setValue(velocity.x, velocity.y, velocity.z);
-        impostor.physicsBody.setLinearVelocity(this._tmpAmmoVectorA);
+        if (!impostor.soft) {
+            this._tmpAmmoVectorA.setValue(velocity.x, velocity.y, velocity.z);
+            impostor.physicsBody.setLinearVelocity(this._tmpAmmoVectorA);
+        }
+        else {
+            Logger.Warn("Linear velocity cannot be applied to a soft body");
+        }
     }
 
     /**
@@ -588,8 +784,13 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
      * @param velocity velocity to set
      */
     public setAngularVelocity(impostor: PhysicsImpostor, velocity: Vector3) {
-        this._tmpAmmoVectorA.setValue(velocity.x, velocity.y, velocity.z);
-        impostor.physicsBody.setAngularVelocity(this._tmpAmmoVectorA);
+        if (!impostor.soft) {
+            this._tmpAmmoVectorA.setValue(velocity.x, velocity.y, velocity.z);
+            impostor.physicsBody.setAngularVelocity(this._tmpAmmoVectorA);
+        }
+        else {
+            Logger.Warn("Angular velocity cannot be applied to a soft body");
+        }
     }
 
     /**
@@ -598,6 +799,10 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
      * @returns linear velocity
      */
     public getLinearVelocity(impostor: PhysicsImpostor): Nullable<Vector3> {
+        if (impostor.soft) {
+            Logger.Warn("Linear velocity is not a property of a soft body");
+            return null;
+        }
         var v = impostor.physicsBody.getLinearVelocity();
         if (!v) {
             return null;
@@ -611,6 +816,10 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
      * @returns angular velocity
      */
     public getAngularVelocity(impostor: PhysicsImpostor): Nullable<Vector3> {
+        if (impostor.soft) {
+            Logger.Warn("Angular velocity is not a property a soft body");
+            return null;
+        }
         var v = impostor.physicsBody.getAngularVelocity();
         if (!v) {
             return null;
@@ -624,7 +833,12 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
      * @param mass mass to set
      */
     public setBodyMass(impostor: PhysicsImpostor, mass: number) {
-        impostor.physicsBody.setMassProps(mass);
+        if (impostor.soft) {
+            impostor.physicsBody.setTotalMass(mass, false);
+        }
+        else {
+            impostor.physicsBody.setMassProps(mass);
+        }
         impostor._pluginData.mass = mass;
     }
 
@@ -652,7 +866,12 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
      * @param friction friction value
      */
     public setBodyFriction(impostor: PhysicsImpostor, friction: number) {
-        impostor.physicsBody.setFriction(friction);
+        if (impostor.soft) {
+            impostor.physicsBody.get_m_cfg().set_kDF(friction);
+        }
+        else {
+            impostor.physicsBody.setFriction(friction);
+        }
         impostor._pluginData.friction = friction;
     }
 
@@ -676,6 +895,122 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
     }
 
     /**
+     * Gets pressure inside the impostor
+     * @param impostor impostor to get pressure from
+     * @returns pressure value
+     */
+    public getBodyPressure(impostor: PhysicsImpostor): number {
+        if (!impostor.soft) {
+            Logger.Warn("Pressure is not a property of a rigid body");
+            return 0;
+        }
+        return impostor._pluginData.pressure;
+    }
+
+    /**
+     * Sets pressure inside the impostor
+     * @param impostor impostor to set pressure on
+     * @param pressure pressure value
+     */
+    public setBodyPressure(impostor: PhysicsImpostor, pressure: number) {
+        if (impostor.soft) {
+            impostor.physicsBody.get_m_cfg().set_kPR(pressure);
+            impostor._pluginData.pressure = pressure;
+        }
+        else {
+            Logger.Warn("Pressure cannot be applied to a rigid body");
+        }
+    }
+
+    /**
+     * Gets stiffness of the impostor
+     * @param impostor impostor to get stiffness from
+     * @returns pressure value
+     */
+    public getBodyStiffness(impostor: PhysicsImpostor): number {
+        if (!impostor.soft) {
+            Logger.Warn("Stiffness is not a property of a rigid body");
+            return 0;
+        }
+        return impostor._pluginData.stiffness;
+    }
+
+    /**
+     * Sets stiffness of the impostor
+     * @param impostor impostor to set stiffness on
+     * @param stiffness stiffness value from 0 to 1
+     */
+    public setBodyStiffness(impostor: PhysicsImpostor, stiffness: number) {
+        if (impostor.soft) {
+            stiffness = stiffness < 0 ? 0 : stiffness;
+            stiffness = stiffness > 1 ? 1 : stiffness;
+            impostor.physicsBody.get_m_materials().at(0).set_m_kLST(stiffness);
+            impostor._pluginData.stiffness = stiffness;
+        }
+        else {
+            Logger.Warn("Stiffness cannot be applied to a rigid body");
+        }
+    }
+
+    /**
+     * Gets velocityIterations of the impostor
+     * @param impostor impostor to get velocity iterations from
+     * @returns velocityIterations value
+     */
+    public getBodyVelocityIterations(impostor: PhysicsImpostor): number {
+        if (!impostor.soft) {
+            Logger.Warn("Velocity iterations is not a property of a rigid body");
+            return 0;
+        }
+        return impostor._pluginData.velocityIterations;
+    }
+
+    /**
+     * Sets velocityIterations of the impostor
+     * @param impostor impostor to set velocity iterations on
+     * @param velocityIterations velocityIterations value
+     */
+    public setBodyVelocityIterations(impostor: PhysicsImpostor, velocityIterations: number) {
+        if (impostor.soft) {
+            velocityIterations = velocityIterations < 0 ? 0 : velocityIterations;
+            impostor.physicsBody.get_m_cfg().set_viterations(velocityIterations);
+            impostor._pluginData.velocityIterations = velocityIterations;
+        }
+        else {
+            Logger.Warn("Velocity iterations cannot be applied to a rigid body");
+        }
+    }
+
+    /**
+     * Gets positionIterations of the impostor
+     * @param impostor impostor to get position iterations from
+     * @returns positionIterations value
+     */
+    public getBodyPositionIterations(impostor: PhysicsImpostor): number {
+        if (!impostor.soft) {
+            Logger.Warn("Position iterations is not a property of a rigid body");
+            return 0;
+        }
+        return impostor._pluginData.positionIterations;
+    }
+
+    /**
+     * Sets positionIterations of the impostor
+     * @param impostor impostor to set position on
+     * @param positionIterations positionIterations value
+     */
+    public setBodyPositionIterations(impostor: PhysicsImpostor, positionIterations: number) {
+        if (impostor.soft) {
+            positionIterations = positionIterations < 0 ? 0 : positionIterations;
+            impostor.physicsBody.get_m_cfg().set_piterations(positionIterations);
+            impostor._pluginData.positionIterations = positionIterations;
+        }
+        else {
+            Logger.Warn("Position iterations cannot be applied to a rigid body");
+        }
+    }
+
+    /**
      * Sleeps the physics body and stops it from being active
      * @param impostor impostor to sleep
      */

+ 1 - 0
src/Physics/physicsEngine.ts

@@ -33,6 +33,7 @@ export class PhysicsEngine implements IPhysicsEngine {
 
     /**
      * Creates a new Physics Engine
+     * Only AmmoJSPlugin allows soft bodies
      * @param gravity defines the gravity vector used by the simulation
      * @param _physicsPlugin defines the plugin to use (CannonJS by default)
      */

+ 128 - 1
src/Physics/physicsImpostor.ts

@@ -10,6 +10,7 @@ import { Bone } from "../Bones/bone";
 import { BoundingInfo } from "../Culling/boundingInfo";
 import { IPhysicsEngine } from "./IPhysicsEngine";
 import { PhysicsJoint, PhysicsJointData } from "./physicsJoint";
+
 /**
  * The interface for the physics imposter parameters
  * @see https://doc.babylonjs.com/how_to/using_the_physics_engine
@@ -39,6 +40,23 @@ export interface PhysicsImpostorParameters {
      * Specifies if bi-directional transformations should be disabled
      */
     disableBidirectionalTransformation?: boolean;
+    /**
+     * The pressure inside the physics imposter, softbody only
+     */
+    pressure?: number;
+    /**
+     * The stiffness the physics imposter, softbody only
+     */
+    stiffness?: number;
+    /**
+     * The number of iterations used in maintaining consistent vertex velocities, softbody only
+     */
+    velocityIterations?: number;
+    /**
+     * The number of iterations used in maintaining consistent vertex positions, softbody only
+     */
+    positionIterations?: number;
+
 }
 
 /**
@@ -245,11 +263,96 @@ export class PhysicsImpostor {
     }
 
     /**
+     * Gets the pressure
+     */
+    get pressure(): number {
+        if (!this._physicsEngine) {
+            return 0;
+        }
+        return this._physicsEngine.getPhysicsPlugin().getBodyPressure!(this);
+    }
+
+    /**
+     * Sets pressure
+     */
+    set pressure(value: number) {
+        if (!this._physicsEngine) {
+            return;
+        }
+        this._physicsEngine.getPhysicsPlugin().setBodyPressure!(this, value);
+    }
+
+    /**
+     * Gets the stiffness
+     */
+    get stiffness(): number {
+        if (!this._physicsEngine) {
+            return 0;
+        }
+        return this._physicsEngine.getPhysicsPlugin().getBodyStiffness!(this);
+    }
+
+    /**
+     * Sets the stiffness
+     */
+    set stiffness(value: number) {
+        if (!this._physicsEngine) {
+            return;
+        }
+        this._physicsEngine.getPhysicsPlugin().setBodyStiffness!(this, value);
+    }
+
+    /**
+     * Gets the velocityIterations
+     */
+    get velocityIterations(): number {
+        if (!this._physicsEngine) {
+            return 0;
+        }
+        return this._physicsEngine.getPhysicsPlugin().getBodyVelocityIterations!(this);
+    }
+
+    /**
+     * Sets the velocityIterations
+     */
+    set velocityIterations(value: number) {
+        if (!this._physicsEngine) {
+            return;
+        }
+        this._physicsEngine.getPhysicsPlugin().setBodyVelocityIterations!(this, value);
+    }
+
+    /**
+     * Gets the positionIterations
+     */
+    get positionIterations(): number {
+        if (!this._physicsEngine) {
+            return 0;
+        }
+        return this._physicsEngine.getPhysicsPlugin().getBodyPositionIterations!(this);
+    }
+
+    /**
+     * Sets the positionIterations
+     */
+    set positionIterations(value: number) {
+        if (!this._physicsEngine) {
+            return;
+        }
+        this._physicsEngine.getPhysicsPlugin().setBodyPositionIterations!(this, value);
+    }
+
+    /**
      * The unique id of the physics imposter
      * set by the physics engine when adding this impostor to the array
      */
     public uniqueId: number;
 
+    /**
+     * @hidden
+     */
+    public soft: boolean = false;
+
     private _joints: Array<{
         joint: PhysicsJoint,
         otherImpostor: PhysicsImpostor
@@ -287,6 +390,10 @@ export class PhysicsImpostor {
             return;
         }
 
+        if (this.type > 100) {
+            this.soft = true;
+        }
+
         this._physicsEngine = this._scene.getPhysicsEngine();
         if (!this._physicsEngine) {
             Logger.Error("Physics not enabled. Please use scene.enablePhysics(...) before creating impostors.");
@@ -304,7 +411,15 @@ export class PhysicsImpostor {
             this._options.mass = (_options.mass === void 0) ? 0 : _options.mass;
             this._options.friction = (_options.friction === void 0) ? 0.2 : _options.friction;
             this._options.restitution = (_options.restitution === void 0) ? 0.2 : _options.restitution;
-
+            this._options.pressure = (_options.pressure === void 0) ? 0 : _options.pressure;
+            if (this.soft) {
+                //softbody mass must be above 0;
+                this._options.mass = this._options.mass > 0 ? this._options.mass : 1;
+                this._options.pressure = (_options.pressure === void 0) ? 200 : _options.pressure;
+                this._options.stiffness = (_options.stiffness === void 0) ? 1 : _options.stiffness;
+                this._options.velocityIterations = (_options.velocityIterations === void 0) ? 20 : _options.velocityIterations;
+                this._options.positionIterations = (_options.positionIterations === void 0) ? 20 : _options.positionIterations;
+            }
             this._joints = [];
             //If the mesh has a parent, don't initialize the physicsBody. Instead wait for the parent to do that.
             if (!this.object.parent || this._options.ignoreParent) {
@@ -1025,4 +1140,16 @@ export class PhysicsImpostor {
      * Heightmap-Imposter type
      */
     public static HeightmapImpostor = 9;
+    /**
+     * Rope-Imposter type
+     */
+    public static RopeImpostor = 101;
+    /**
+     * Cloth-Imposter type
+     */
+    public static ClothImpostor = 102;
+    /**
+     * Softbody-Imposter type
+     */
+    public static SoftbodyImpostor = 103;
 }