David Catuhe 8 лет назад
Родитель
Сommit
f96d1ec01d

+ 5 - 1
src/Physics/Plugins/babylon.cannonJSPlugin.ts

@@ -28,6 +28,10 @@
             this._fixedTimeStep = timeStep;
         }
 
+        public getTimeStep(): number {
+          return this._fixedTimeStep;
+        }
+
         public executeStep(delta: number, impostors: Array<PhysicsImpostor>): void {
             // Delta is in seconds, should be provided in milliseconds
             this.world.step(this._fixedTimeStep, this._useDeltaForWorldStep ? delta * 1000 : 0, 3);
@@ -376,7 +380,7 @@
                 var translation = mesh.getBoundingInfo().boundingBox.centerWorld.subtract(center).subtract(mesh.position).negate();
 
                 this._tmpPosition.copyFromFloats(translation.x, translation.y - mesh.getBoundingInfo().boundingBox.extendSizeWorld.y, translation.z);
-                //add it inverted to the delta 
+                //add it inverted to the delta
                 this._tmpDeltaPosition.copyFrom(mesh.getBoundingInfo().boundingBox.centerWorld.subtract(c));
                 this._tmpDeltaPosition.y += mesh.getBoundingInfo().boundingBox.extendSizeWorld.y;
 

+ 6 - 2
src/Physics/Plugins/babylon.oimoJSPlugin.ts

@@ -22,6 +22,10 @@ module BABYLON {
             this.world.timeStep = timeStep;
         }
 
+        public getTimeStep(): number {
+          return this.world.timeStep;
+        }
+
         private _tmpImpostorsArray: Array<PhysicsImpostor> = [];
 
         public executeStep(delta: number, impostors: Array<PhysicsImpostor>) {
@@ -414,7 +418,7 @@ module BABYLON {
             mesh.position.x = body.position.x;
             mesh.position.y = body.position.y;
             mesh.position.z = body.position.z;
-            
+
             mesh.rotationQuaternion.x = body.orientation.x;
             mesh.rotationQuaternion.y = body.orientation.y;
             mesh.rotationQuaternion.z = body.orientation.z;
@@ -436,4 +440,4 @@ module BABYLON {
             this.world.clear();
         }
     }
-}
+}

+ 15 - 7
src/Physics/babylon.physicsEngine.ts

@@ -24,10 +24,10 @@
             this.gravity = gravity;
             this._physicsPlugin.setGravity(this.gravity);
         }
-        
+
         /**
          * Set the time step of the physics engine.
-         * default is 1/60. 
+         * default is 1/60.
          * To slow it down, enter 1/600 for example.
          * To speed it up, 1/30
          * @param {number} newTimeStep the new timestep to apply to this world.
@@ -36,6 +36,13 @@
             this._physicsPlugin.setTimeStep(newTimeStep);
         }
 
+        /**
+         * Get the time step of the physics engine.
+         */
+        public getTimeStep(): number {
+            return this._physicsPlugin.getTimeStep();
+        }
+
         public dispose(): void {
             this._impostors.forEach(function (impostor) {
                 impostor.dispose();
@@ -51,7 +58,7 @@
         public static Epsilon = 0.001;
 
         //new methods and parameters
-        
+
         private _impostors: Array<PhysicsImpostor> = [];
         private _joints: Array<PhysicsImpostorJoint> = [];
 
@@ -84,7 +91,7 @@
                 }
             }
         }
-        
+
         /**
          * Add a joint to the physics engine
          * @param {PhysicsImpostor} mainImpostor the main impostor to which the joint is added.
@@ -111,7 +118,7 @@
             if (matchingJoints.length) {
                 this._physicsPlugin.removeJoint(matchingJoints[0]);
                 //TODO remove it from the list as well
-                
+
             }
         }
 
@@ -139,7 +146,7 @@
         public getPhysicsPlugin(): IPhysicsEnginePlugin {
             return this._physicsPlugin;
         }
-        
+
         public getImpostorForPhysicsObject(object: IPhysicsEnabledObject) {
             for (var i = 0; i < this._impostors.length; ++i) {
                 if (this._impostors[i].object === object) {
@@ -162,6 +169,7 @@
         name: string;
         setGravity(gravity: Vector3);
         setTimeStep(timeStep: number);
+        getTimeStep(): number;
         executeStep(delta: number, impostors: Array<PhysicsImpostor>): void; //not forgetting pre and post events
         applyImpulse(impostor: PhysicsImpostor, force: Vector3, contactPoint: Vector3);
         applyForce(impostor: PhysicsImpostor, force: Vector3, contactPoint: Vector3);
@@ -193,4 +201,4 @@
         syncMeshWithImpostor(mesh:AbstractMesh, impostor:PhysicsImpostor);
         dispose();
     }
-}
+}

+ 41 - 16
src/babylon.engine.ts

@@ -231,6 +231,8 @@
         autoEnableWebVR?: boolean;
         disableWebGL2Support?: boolean;
         audioEngine?: boolean;
+        deterministicLockstep?: boolean;
+        lockstepMaxSteps?: number;
     }
 
     /**
@@ -522,7 +524,7 @@
          */
         public onCanvasBlurObservable = new Observable<Engine>();
 
-        //WebVR 
+        //WebVR
 
         //The new WebVR uses promises.
         //this promise resolves with the current devices available.
@@ -581,6 +583,10 @@
         private _renderingQueueLaunched = false;
         private _activeRenderLoops = [];
 
+        // Deterministic lockstepMaxSteps
+        private _deterministicLockstep: boolean = false;
+        private _lockstepMaxSteps: number = 4;
+
         // FPS
         private _performanceMonitor = new PerformanceMonitor();
         private _fps = 60;
@@ -683,6 +689,14 @@
                     options.antialias = antialias;
                 }
 
+                if (options.deterministicLockstep === undefined) {
+                    options.deterministicLockstep = false;
+                }
+
+                if (options.lockstepMaxSteps === undefined) {
+                    options.lockstepMaxSteps = 4;
+                }
+
                 if (options.preserveDrawingBuffer === undefined) {
                     options.preserveDrawingBuffer = false;
                 }
@@ -695,6 +709,9 @@
                     options.stencil = true;
                 }
 
+                this._deterministicLockstep = options.deterministicLockstep;
+                this._lockstepMaxSteps = options.lockstepMaxSteps;
+
                 // GL
                 if (!options.disableWebGL2Support) {
                     try {
@@ -789,7 +806,7 @@
             // Constants
             this._gl.HALF_FLOAT_OES = 0x8D61; // Half floating-point type (16-bit).
             this._gl.RGBA16F = 0x881A; // RGBA 16-bit floating-point color-renderable internal sized format.
-            this._gl.RGBA32F = 0x8814; // RGBA 32-bit floating-point color-renderable internal sized format.         
+            this._gl.RGBA32F = 0x8814; // RGBA 32-bit floating-point color-renderable internal sized format.
             this._gl.DEPTH24_STENCIL8 = 35056;
 
             // Extensions
@@ -825,7 +842,7 @@
 
             this._caps.textureLOD = this._webGLVersion > 1 || this._gl.getExtension('EXT_shader_texture_lod');
 
-            // Vertex array object 
+            // Vertex array object
             if (this._webGLVersion > 1) {
                 this._caps.vertexArrayObject = true;
             } else {
@@ -840,7 +857,7 @@
                     this._caps.vertexArrayObject = false;
                 }
             }
-            // Instances count            
+            // Instances count
             if (this._webGLVersion > 1) {
                 this._caps.instancedArrays = true;
             } else {
@@ -858,7 +875,7 @@
 
             // Intelligently add supported compressed formats in order to check for.
             // Check for ASTC support first as it is most powerful and to be very cross platform.
-            // Next PVRTC & DXT, which are probably superior to ETC1/2.  
+            // Next PVRTC & DXT, which are probably superior to ETC1/2.
             // Likely no hardware which supports both PVR & DXT, so order matters little.
             // ETC2 is newer and handles ETC1 (no alpha capability), so check for first.
             if (this._caps.astc) this.texturesSupported.push('-astc.ktx');
@@ -974,6 +991,14 @@
             }
         }
 
+        public isDeterministicLockStep(): boolean {
+          return this._deterministicLockstep;
+        }
+
+        public getLockstepMaxSteps(): number {
+          return this._lockstepMaxSteps;
+        }
+
         public getGlInfo() {
             return {
                 vendor: this._glVendor,
@@ -1456,7 +1481,7 @@
             }
 
             if (this._cachedViewport && !forceFullscreenViewport) {
-                this.setViewport(this._cachedViewport, requiredWidth, requiredHeight);            
+                this.setViewport(this._cachedViewport, requiredWidth, requiredHeight);
             } else {
                 gl.viewport(0, 0, requiredWidth || texture._width, requiredHeight || texture._height);
             }
@@ -2300,7 +2325,7 @@
 
         // States
         public setState(culling: boolean, zOffset: number = 0, force?: boolean, reverseSide = false): void {
-            // Culling        
+            // Culling
             var showSide = reverseSide ? this._gl.FRONT : this._gl.BACK;
             var hideSide = reverseSide ? this._gl.BACK : this._gl.FRONT;
             var cullFace = this.cullBackFaces ? showSide : hideSide;
@@ -2422,7 +2447,7 @@
             this.resetTextureCache();
             this._currentEffect = null;
 
-            // 6/8/2017: deltakosh: Should not be required anymore. 
+            // 6/8/2017: deltakosh: Should not be required anymore.
             // This message is then mostly for the future myself which will scream out loud when seeing that actually it was required :)
             if (bruteForce) {
                 this._currentProgram = null;
@@ -2444,20 +2469,20 @@
         /**
          * Set the compressed texture format to use, based on the formats you have, and the formats
          * supported by the hardware / browser.
-         * 
+         *
          * Khronos Texture Container (.ktx) files are used to support this.  This format has the
          * advantage of being specifically designed for OpenGL.  Header elements directly correspond
          * to API arguments needed to compressed textures.  This puts the burden on the container
          * generator to house the arcane code for determining these for current & future formats.
-         * 
+         *
          * for description see https://www.khronos.org/opengles/sdk/tools/KTX/
          * for file layout see https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
-         * 
+         *
          * Note: The result of this call is not taken into account when a texture is base64.
-         * 
+         *
          * @param {Array<string>} formatsAvailable- The list of those format families you have created
          * on your server.  Syntax: '-' + format family + '.ktx'.  (Case and order do not matter.)
-         * 
+         *
          * Current families are astc, dxt, pvrtc, etc2, & etc1.
          * @returns The extension selected.
          */
@@ -2490,7 +2515,7 @@
          * @param {ArrayBuffer | HTMLImageElement} buffer- A source of a file previously fetched as either an ArrayBuffer (compressed or image format) or HTMLImageElement (image format)
          * @param {WebGLTexture} fallback- An internal argument in case the function must be called again, due to etc1 not having alpha capabilities.
          * @param {number} format-  Internal format.  Default: RGB when extension is '.jpg' else RGBA.  Ignored for compressed textures.
-         * 
+         *
          * @returns {WebGLTexture} for assignment back into BABYLON.Texture
          */
         public createTexture(urlArg: string, noMipmap: boolean, invertY: boolean, scene: Scene, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE, onLoad: () => void = null, onError: () => void = null, buffer: ArrayBuffer | HTMLImageElement = null, fallBack?: WebGLTexture, format?: number): WebGLTexture {
@@ -3953,7 +3978,7 @@
 
             var anisotropicFilterExtension = this._caps.textureAnisotropicFilterExtension;
             var value = texture.anisotropicFilteringLevel;
-            
+
 
             if (internalTexture.samplingMode === Texture.NEAREST_SAMPLINGMODE) {
                 value = 1;
@@ -4366,4 +4391,4 @@
             }
         }
     }
-}
+}

+ 97 - 21
src/babylon.scene.ts

@@ -221,7 +221,7 @@
          * Forward main pass or through the imageProcessingPostProcess if present.
          * As in the majority of the scene they are the same (exception for multi camera),
          * this is easier to reference from here than from all the materials and post process.
-         * 
+         *
          * No setter as we it is a shared configuration, you can set the values instead.
          */
         public get imageProcessingConfiguration(): ImageProcessingConfiguration {
@@ -384,6 +384,18 @@
         public onMeshRemovedObservable = new Observable<AbstractMesh>();
 
         /**
+        * An event triggered before calculating deterministic simulation step
+        * @type {BABYLON.Observable}
+        */
+        public onBeforeStepObservable = new Observable<Scene>();
+
+        /**
+        * An event triggered after calculating deterministic simulation step
+        * @type {BABYLON.Observable}
+        */
+        public onAfterStepObservable = new Observable<Scene>();
+
+        /**
          * This Observable will be triggered for each stage of each renderingGroup of each rendered camera.
          * The RenderinGroupInfo class contains all the information about the context in which the observable is called
          * If you wish to register an Observer only for a given set of renderingGroup, use the mask with a combination of the renderingGroup index elevated to the power of two (1 for renderingGroup 0, 2 for renderingrOup1, 4 for 2 and 8 for 3)
@@ -459,6 +471,11 @@
         private _startingPointerTime = 0;
         private _previousStartingPointerTime = 0;
 
+        // Deterministic lockstep
+        private _timeAccumulator: number = 0;
+        private _currentStepId: number = 0;
+        private _currentInternalStep: number = 0;
+
         // Mirror
         public _mirroredCameraPosition: Vector3;
 
@@ -483,6 +500,18 @@
             return this._useRightHandedSystem;
         }
 
+        public setStepId(newStepId: number): void {
+            this._currentStepId = newStepId;
+        };
+
+        public getStepId(): number {
+            return this._currentStepId;
+        };
+
+        public getInternalStep(): number {
+            return this._currentInternalStep;
+        };
+
         // Fog
 
         private _fogEnabled = true;
@@ -1597,6 +1626,8 @@
             this._cachedVisibility = null;
         }
 
+
+
         public registerBeforeRender(func: () => void): void {
             this.onBeforeRenderObservable.add(func);
         }
@@ -1662,7 +1693,7 @@
         // Animations
         /**
          * Will start the animation sequence of a given target
-         * @param target - the target 
+         * @param target - the target
          * @param {number} from - from which frame should animation start
          * @param {number} to - till which frame should animation run.
          * @param {boolean} [loop] - should the animation loop
@@ -1728,7 +1759,7 @@
 
         /**
          * Will stop the animation of the given target
-         * @param target - the target 
+         * @param target - the target
          * @param animationName - the name of the animation to stop (all animations will be stopped is empty)
          * @see beginAnimation
          */
@@ -1827,7 +1858,7 @@
         public removeMesh(toRemove: AbstractMesh): number {
             var index = this.meshes.indexOf(toRemove);
             if (index !== -1) {
-                // Remove from the scene if mesh found 
+                // Remove from the scene if mesh found
                 this.meshes.splice(index, 1);
             }
             //notify the collision coordinator
@@ -1843,7 +1874,7 @@
         public removeSkeleton(toRemove: Skeleton): number {
             var index = this.skeletons.indexOf(toRemove);
             if (index !== -1) {
-                // Remove from the scene if found 
+                // Remove from the scene if found
                 this.skeletons.splice(index, 1);
             }
 
@@ -1853,7 +1884,7 @@
         public removeMorphTargetManager(toRemove: MorphTargetManager): number {
             var index = this.morphTargetManagers.indexOf(toRemove);
             if (index !== -1) {
-                // Remove from the scene if found 
+                // Remove from the scene if found
                 this.morphTargetManagers.splice(index, 1);
             }
 
@@ -1863,7 +1894,7 @@
         public removeLight(toRemove: Light): number {
             var index = this.lights.indexOf(toRemove);
             if (index !== -1) {
-                // Remove from the scene if mesh found 
+                // Remove from the scene if mesh found
                 this.lights.splice(index, 1);
                 this.sortLightsByPriority();
             }
@@ -1874,7 +1905,7 @@
         public removeCamera(toRemove: Camera): number {
             var index = this.cameras.indexOf(toRemove);
             if (index !== -1) {
-                // Remove from the scene if mesh found 
+                // Remove from the scene if mesh found
                 this.cameras.splice(index, 1);
             }
             // Remove from activeCameras
@@ -2414,7 +2445,7 @@
         /**
          * Return a the first highlight layer of the scene with a given name.
          * @param name The name of the highlight layer to look for.
-         * @return The highlight layer if found otherwise null. 
+         * @return The highlight layer if found otherwise null.
          */
         public getHighlightLayerByName(name: string): HighlightLayer {
             for (var index = 0; index < this.highlightLayers.length; index++) {
@@ -2926,16 +2957,61 @@
                 this.simplificationQueue.executeNext();
             }
 
-            // Animations
-            var deltaTime = Math.max(Scene.MinDeltaTime, Math.min(this._engine.getDeltaTime(), Scene.MaxDeltaTime));
-            this._animationRatio = deltaTime * (60.0 / 1000.0);
-            this._animate();
+            if(this._engine.isDeterministicLockStep()){
+              var deltaTime = Math.max(Scene.MinDeltaTime, Math.min(this._engine.getDeltaTime(), Scene.MaxDeltaTime)) / 1000;
 
-            // Physics
-            if (this._physicsEngine) {
-                Tools.StartPerformanceCounter("Physics");
-                this._physicsEngine._step(deltaTime / 1000.0);
-                Tools.EndPerformanceCounter("Physics");
+              var defaultTimeStep = (60.0 / 1000.0);
+              if (this._physicsEngine) {
+                defaultTimeStep = this._physicsEngine.getTimeStep();
+              }
+
+              var maxSubSteps = this._engine.getLockstepMaxSteps();
+
+              this._timeAccumulator += deltaTime;
+
+              // compute the amount of fixed steps we should have taken since the last step
+              var internalSteps = Math.floor(this._timeAccumulator / defaultTimeStep);
+              internalSteps = Math.min(internalSteps, maxSubSteps);
+
+              for(this._currentInternalStep = 0; this._currentInternalStep < internalSteps; this._currentInternalStep++){
+
+                this.onBeforeStepObservable.notifyObservers(this);
+
+                // Animations
+                this._animationRatio = defaultTimeStep * (60.0 / 1000.0);
+                this._animate();
+
+                // Physics
+                if (this._physicsEngine) {
+                   Tools.StartPerformanceCounter("Physics");
+                   this._physicsEngine._step(defaultTimeStep);
+                   Tools.EndPerformanceCounter("Physics");
+                }
+                this._timeAccumulator -= defaultTimeStep;
+
+                this.onAfterStepObservable.notifyObservers(this);
+                this._currentStepId++;
+
+                if((internalSteps > 1) && (this._currentInternalStep != internalSteps - 1)) {
+                    // Q: can this be optimized by putting some code in the afterStep callback?
+                    // I had to put this code here, otherwise mesh attached to bones of another mesh skeleton,
+                    // would return incorrect positions for internal stepIds (non-rendered steps)
+                    this._evaluateActiveMeshes();
+                }
+              }
+            }
+            else {
+              // Animations
+              var deltaTime = Math.max(Scene.MinDeltaTime, Math.min(this._engine.getDeltaTime(), Scene.MaxDeltaTime));
+              this._animationRatio = deltaTime * (60.0 / 1000.0);
+              this._animate();
+
+              // Physics
+              if (this._physicsEngine) {
+                 Tools.StartPerformanceCounter("Physics");
+                 this._physicsEngine._step(deltaTime / 1000.0);
+                 Tools.EndPerformanceCounter("Physics");
+              }
             }
 
             // Before render
@@ -3273,7 +3349,7 @@
                 this._depthRenderer.dispose();
             }
 
-            // Smart arrays            
+            // Smart arrays
             if (this.activeCamera) {
                 this.activeCamera._activeMeshes.dispose();
                 this.activeCamera = null;
@@ -3872,7 +3948,7 @@
         /**
          * Overrides the default sort function applied in the renderging group to prepare the meshes.
          * This allowed control for front to back rendering or reversly depending of the special needs.
-         * 
+         *
          * @param renderingGroupId The rendering group id corresponding to its index
          * @param opaqueSortCompareFn The opaque queue comparison function use to sort.
          * @param alphaTestSortCompareFn The alpha test queue comparison function use to sort.
@@ -3891,7 +3967,7 @@
 
         /**
          * Specifies whether or not the stencil and depth buffer are cleared between two rendering groups.
-         * 
+         *
          * @param renderingGroupId The rendering group id corresponding to its index
          * @param autoClearDepthStencil Automatically clears depth and stencil between groups if true.
          * @param depth Automatically clears depth between groups if true and autoClear is true.