Selaa lähdekoodia

Merge pull request #5995 from BabylonJSGuide/master

Added Rope as a soft body to AmmoJSPlugin
David Catuhe 6 vuotta sitten
vanhempi
commit
236d9d3b12

+ 1 - 1
dist/preview release/what's new.md

@@ -6,7 +6,7 @@
 - Added support for [parallel shader compilation](https://www.khronos.org/registry/webgl/extensions/KHR_parallel_shader_compile/) ([Deltakosh](https://github.com/deltakosh))
 - Added [Object Based Motion Blur](http://doc.babylonjs.com/how_to/using_motionblurpostprocess) post-process ([julien-moreau](https://github.com/julien-moreau))
 - Added [support for AmmoJS](https://doc.babylonjs.com/how_to/using_the_physics_engine) as a physics plugin (Composite objects, motors, joints) ([TrevorDev](https://github.com/TrevorDev))
-  - Added support for soft bodies in Ammo physics plugin. [Doc](https://doc.babylonjs.com/how_to/soft_bodies) ([JohnK](https://github.com/BabylonJSGuide))
+  - Added support for soft bodies, which are 3D softbody, 2D cloth and 1D rope, in Ammo physics plugin. [Doc](https://doc.babylonjs.com/how_to/soft_bodies) ([JohnK](https://github.com/BabylonJSGuide))
   - Added support for [Convex Hull Impostor][https://github.com/kripken/ammo.js/blob/master/bullet/src/BulletCollision/CollisionShapes/btConvexHullShape.h] using Ammo.js plugin ([MackeyK24](https://github.com/mackeyk24))
 - Added support for [WebXR](https://doc.babylonjs.com/how_to/webxr) ([TrevorDev](https://github.com/TrevorDev))
   - Add customAnimationFrameRequester to allow sessions to hook into engine's render loop ([TrevorDev](https://github.com/TrevorDev))

+ 1 - 0
src/Physics/IPhysicsEngine.ts

@@ -53,6 +53,7 @@ export interface IPhysicsEnginePlugin {
     getBodyPositionIterations?(impostor: PhysicsImpostor): number;
     setBodyPositionIterations?(impostor: PhysicsImpostor, positionIterations: number): void;
     appendAnchor?(impostor: PhysicsImpostor, otherImpostor: PhysicsImpostor, width: number, height: number, influence: number, noCollisionBetweenLinkedBodies: boolean): void;
+    appendHook?(impostor: PhysicsImpostor, otherImpostor: PhysicsImpostor, length: number, influence: number, noCollisionBetweenLinkedBodies: boolean): void;
     sleepBody(impostor: PhysicsImpostor): void;
     wakeUpBody(impostor: PhysicsImpostor): void;
     raycast(from: Vector3, to: Vector3): PhysicsRaycastResult;

+ 136 - 10
src/Physics/Plugins/ammoJSPlugin.ts

@@ -8,6 +8,9 @@ import { VertexData } from "../../Meshes/mesh.vertexData";
 import { Nullable } from "../../types";
 import { AbstractMesh } from "../../Meshes/abstractMesh";
 import { Mesh } from "../../Meshes/mesh";
+import { ShapeBuilder } from "../../Meshes/Builders/shapeBuilder";
+import { LinesBuilder } from "../../Meshes/Builders/linesBuilder";
+import { LinesMesh } from '../../Meshes/linesMesh';
 import { PhysicsRaycastResult } from "../physicsRaycastResult";
 
 declare var Ammo: any;
@@ -194,7 +197,7 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
         for (var mainImpostor of impostors) {
             // After physics update make babylon world objects match physics world objects
             if (mainImpostor.soft) {
-                this.afterSoftStep(mainImpostor);
+                this._afterSoftStep(mainImpostor);
             }
             else {
                 mainImpostor.afterStep();
@@ -219,10 +222,53 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
     }
 
     /**
-     * Update babylon mesh vertices vertices to match physics world object
+     * Update babylon mesh to match physics world object
      * @param impostor imposter to match
      */
-    public afterSoftStep(impostor: PhysicsImpostor): void {
+    private _afterSoftStep(impostor: PhysicsImpostor): void {
+        if (impostor.type === PhysicsImpostor.RopeImpostor) {
+            this._ropeStep(impostor);
+        }
+        else {
+            this._softbodyOrClothStep(impostor);
+        }
+    }
+
+    /**
+     * Update babylon mesh vertices vertices to match physics world softbody or cloth
+     * @param impostor imposter to match
+     */
+    private _ropeStep(impostor: PhysicsImpostor): void {
+        var bodyVertices = impostor.physicsBody.get_m_nodes();
+        var nbVertices = bodyVertices.size();
+        var node: any;
+        var nodePositions: any;
+        var x, y, z: number;
+        var path: Array<Vector3> = new Array();
+        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();
+            path.push(new Vector3(x, y, z));
+        }
+        var object = impostor.object;
+        var shape = impostor.getParam("shape");
+        if (impostor._isFromLine) {
+            impostor.object = LinesBuilder.CreateLines("lines", {points: path, instance: <LinesMesh>object});
+        }
+        else {
+            impostor.object = ShapeBuilder.ExtrudeShape("ext", {shape: shape, path: path, instance: <Mesh>object});
+        }
+
+    }
+
+    /**
+     * Update babylon mesh vertices vertices to match physics world softbody or cloth
+     * @param impostor imposter to match
+     */
+    private _softbodyOrClothStep(impostor: PhysicsImpostor): void {
         var normalDirection = (impostor.type === PhysicsImpostor.ClothImpostor) ? 1 : -1;
         var object = impostor.object;
         var vertexPositions = object.getVerticesData(VertexBuffer.PositionKind);
@@ -628,7 +674,6 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
                     nodeNormals.setY(triNorms[3 * i + 1]);
                     nodeNormals.setZ(triNorms[3 * i + 2]);
                 }
-                softBody.get_m_cfg().set_collisions(0x11);
                 return softBody;
             }
         }
@@ -674,12 +719,76 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
                     impostor.getParam("fixedPoints"),
                     true
                 );
-                clothBody.get_m_cfg().set_collisions(0x11);
                 return clothBody;
             }
         }
     }
 
+    /**
+     * Create rope for an impostor
+     * @param impostor to create the softbody for
+     */
+    private _createRope(impostor: PhysicsImpostor) {
+        var len: number;
+        var segments: number;
+        var vertex_data = this._softVertexData(impostor);
+        var vertexPositions = vertex_data.positions;
+        var vertexNormals = vertex_data.normals;
+
+        if (vertexPositions === null || vertexNormals === null) {
+            return new Ammo.btCompoundShape();
+        }
+
+        //force the mesh to be updatable
+        vertex_data.applyToMesh(<Mesh>impostor.object, true);
+
+        impostor._isFromLine = true;
+
+        // If in lines mesh all normals will be zero
+        var vertexSquared: Array<number> = <Array<number>>vertexNormals.map((x: number) => x * x);
+        var reducer = (accumulator: number, currentValue: number): number => accumulator + currentValue;
+        var reduced: number = vertexSquared.reduce(reducer);
+
+        if (reduced === 0) { // line mesh
+                len = vertexPositions.length;
+                segments = len / 3 - 1;
+                this._tmpAmmoVectorA.setValue(vertexPositions[0], vertexPositions[1], vertexPositions[2]);
+                this._tmpAmmoVectorB.setValue(vertexPositions[len - 3], vertexPositions[len - 2], vertexPositions[len - 1]);
+        }
+        else { //extruded mesh
+            impostor._isFromLine = false;
+            var pathVectors = impostor.getParam("path");
+            var shape = impostor.getParam("shape");
+            if (shape === null) {
+                Logger.Warn("No shape available for extruded mesh");
+                return new Ammo.btCompoundShape();
+            }
+            if ((vertexPositions!.length % (3 * pathVectors.length)) !== 0) {
+                Logger.Warn("Path does not match extrusion");
+                return new Ammo.btCompoundShape();
+            }
+            len = pathVectors.length;
+            segments = len - 1;
+            this._tmpAmmoVectorA.setValue(pathVectors[0].x, pathVectors[0].y, pathVectors[0].z);
+            this._tmpAmmoVectorB.setValue(pathVectors[len - 1].x, pathVectors[len - 1].y, pathVectors[len - 1].z);
+        }
+
+        impostor.segments = segments;
+
+        var fixedPoints = impostor.getParam("fixedPoints");
+        fixedPoints = (fixedPoints > 3) ? 3 : fixedPoints;
+
+        var ropeBody = new Ammo.btSoftBodyHelpers().CreateRope(
+            this.world.getWorldInfo(),
+            this._tmpAmmoVectorA,
+            this._tmpAmmoVectorB,
+            segments - 1,
+            fixedPoints
+        );
+        ropeBody.get_m_cfg().set_collisions(0x11);
+        return ropeBody;
+    }
+
     // adds all verticies (including child verticies) to the convex hull shape
     private _addHullVerts(btConvexHullShape: any, topLevelObject: IPhysicsEnabledObject, object: IPhysicsEnabledObject) {
         var triangleCount = 0;
@@ -827,9 +936,13 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
                 returnValue = this._createSoftbody(impostor);
                 break;
             case PhysicsImpostor.ClothImpostor:
-                // Only usable with a mesh that has sufficient and shared vertices
+                // Only usable with a ground mesh that has sufficient and shared vertices
                 returnValue = this._createCloth(impostor);
                 break;
+            case PhysicsImpostor.RopeImpostor:
+                // Only usable with a line mesh or an extruded mesh that is updatable
+                returnValue = this._createRope(impostor);
+                break;
             default:
                 Logger.Warn("The impostor type is not currently supported by the ammo plugin.");
                 break;
@@ -1159,12 +1272,12 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
     }
 
      /**
-     * Append an anchor to a soft object
-     * @param impostor soft impostor to add anchor to
-     * @param otherImpostor rigid impostor as the anchor
+     * Append an anchor to a cloth object
+     * @param impostor is the cloth impostor to add anchor to
+     * @param otherImpostor is the rigid impostor to anchor to
      * @param width ratio across width from 0 to 1
      * @param height ratio up height from 0 to 1
-     * @param influence the elasticity between soft impostor and anchor from 0, very stretchy to 1, no strech
+     * @param influence the elasticity between cloth impostor and anchor from 0, very stretchy to 1, little strech
      * @param noCollisionBetweenLinkedBodies when true collisions between soft impostor and anchor are ignored; default false
      */
     public appendAnchor(impostor: PhysicsImpostor, otherImpostor: PhysicsImpostor, width: number, height: number, influence: number = 1, noCollisionBetweenLinkedBodies: boolean = false) {
@@ -1177,6 +1290,19 @@ export class AmmoJSPlugin implements IPhysicsEnginePlugin {
     }
 
     /**
+     * Append an hook to a rope object
+     * @param impostor is the rope impostor to add hook to
+     * @param otherImpostor is the rigid impostor to hook to
+     * @param length ratio along the rope from 0 to 1
+     * @param influence the elasticity between soft impostor and anchor from 0, very stretchy to 1, little strech
+     * @param noCollisionBetweenLinkedBodies when true collisions between soft impostor and anchor are ignored; default false
+     */
+    public appendHook(impostor: PhysicsImpostor, otherImpostor: PhysicsImpostor, length: number, influence: number = 1, noCollisionBetweenLinkedBodies: boolean = false) {
+        var node = Math.round(impostor.segments * length);
+        impostor.physicsBody.appendAnchor(node, otherImpostor.physicsBody, noCollisionBetweenLinkedBodies, influence);
+    }
+
+    /**
      * Sleeps the physics body and stops it from being active
      * @param impostor impostor to sleep
      */

+ 39 - 4
src/Physics/physicsImpostor.ts

@@ -70,6 +70,14 @@ export interface PhysicsImpostorParameters {
      * The collision margin around a soft object
      */
     damping?: number;
+    /**
+     * The path for a rope based on an extrusion
+     */
+    path?: any;
+    /**
+     * The shape of an extrusion used for a rope based on an extrusion
+     */
+    shape?: any;
 }
 
 /**
@@ -215,6 +223,9 @@ export class PhysicsImpostor {
     private _deltaRotation: Quaternion;
     private _deltaRotationConjugated: Quaternion;
 
+    /** hidden */
+    public _isFromLine: boolean;
+
     //If set, this is this impostor's parent
     private _parent: Nullable<PhysicsImpostor>;
 
@@ -471,6 +482,8 @@ export class PhysicsImpostor {
                 this._options.fixedPoints = (_options.fixedPoints === void 0) ? 0 : _options.fixedPoints;
                 this._options.margin = (_options.margin === void 0) ? 0 : _options.margin;
                 this._options.damping = (_options.damping === void 0) ? 0 : _options.damping;
+                this._options.path = (_options.path === void 0) ? null : _options.path;
+                this._options.shape = (_options.shape === void 0) ? null : _options.shape;
             }
             this._joints = [];
             //If the mesh has a parent, don't initialize the physicsBody. Instead wait for the parent to do that.
@@ -948,12 +961,12 @@ export class PhysicsImpostor {
     }
 
     /**
-     * Add an anchor to a soft impostor
-     * @param otherImpostor rigid impostor as the anchor
+     * Add an anchor to a cloth impostor
+     * @param otherImpostor rigid impostor to anchor to
      * @param width ratio across width from 0 to 1
      * @param height ratio up height from 0 to 1
-     * @param influence the elasticity between soft impostor and anchor from 0, very stretchy to 1, no strech
-     * @param noCollisionBetweenLinkedBodies when true collisions between soft impostor and anchor are ignored; default false
+     * @param influence the elasticity between cloth impostor and anchor from 0, very stretchy to 1, little strech
+     * @param noCollisionBetweenLinkedBodies when true collisions between cloth impostor and anchor are ignored; default false
      * @returns impostor the soft imposter
      */
     public addAnchor(otherImpostor: PhysicsImpostor, width: number, height: number, influence: number, noCollisionBetweenLinkedBodies: boolean): PhysicsImpostor {
@@ -971,6 +984,28 @@ export class PhysicsImpostor {
     }
 
     /**
+     * Add a hook to a rope impostor
+     * @param otherImpostor rigid impostor to anchor to
+     * @param length ratio across rope from 0 to 1
+     * @param influence the elasticity between rope impostor and anchor from 0, very stretchy to 1, little strech
+     * @param noCollisionBetweenLinkedBodies when true collisions between soft impostor and anchor are ignored; default false
+     * @returns impostor the rope imposter
+     */
+    public addHook(otherImpostor: PhysicsImpostor, length: number, influence: number, noCollisionBetweenLinkedBodies: boolean): PhysicsImpostor {
+        if (!this._physicsEngine) {
+            return this;
+        }
+        const plugin = this._physicsEngine.getPhysicsPlugin();
+        if (!plugin.appendAnchor) {
+            return this;
+        }
+        if (this._physicsEngine) {
+            plugin.appendHook!(this, otherImpostor, length, influence, noCollisionBetweenLinkedBodies);
+        }
+        return this;
+    }
+
+    /**
      * Will keep this body still, in a sleep mode.
      * @returns the physics imposter
      */