Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Clock from three.js for step simulation of bullet world #1206

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

microaaron
Copy link
Contributor

Introduce Clock from three.js for step simulation of bullet world.
Clock.js requires es6.

@andreasplesch
Copy link
Contributor

Yeah, the constant time step must be wrong.

Clock is a small object but it would be great if it would be possible to avoid introducing another object. Are there plans to use it for something else ? Ammo itself has a btClock but it does not seem to do much. Does Ammo have a timer or another clock ? I could not find much.. A new TimeSensor seems like overkill, hm..

The RAF callback actually gets the current time as an argument:

main = function main ()

main() does not use it but it would be possible to keep track of deltaTime there: main( time ). If the other Clock methods are not used that would avoid having to introduce the Clock object. What about passing deltaTime to updateRigidBodies from main: updateRigidBodies( time - lastTime ) ? Could that work ? That could be leaner. Do you want to try that ?

[ In any case, src/util would be a better place for Clock.js

Would there be advantages in using performance.now instead of Date.now ? Perhaps there is a Three.js issue.

Is there a place to destroy/dereference the clock instance when the bulletworld/collidableShape is removed from the scene ? ]

@microaaron
Copy link
Contributor Author

Using a btClock like this:

    initScene = function ()
    {
        var collisionConfiguration,
            dispatcher,
            overlappingPairCache,
            solver,
            WorldGravity = new x3dom.fields.SFVec3f();
        collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
        dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration );
        overlappingPairCache = new Ammo.btDbvtBroadphase();
        solver = new Ammo.btSequentialImpulseConstraintSolver();
        bulletWorld = new Ammo.btDiscreteDynamicsWorld( dispatcher, overlappingPairCache, solver, collisionConfiguration );
        bulletWorld.setGravity( new Ammo.btVector3( 0, -9.81, 0 ) );
        //Add a clock
        var bulletClock = new Ammo.btClock();
        var running = false;
        var lastTime = 0;
        bulletWorld.clock = {
            getDelta : function ()
            {
                var deltaTime;
                if ( running )
                {
                    deltaTime = ( bulletClock.getTimeMilliseconds() - lastTime ) / 1000;
                }
                else
                {
                    bulletClock.reset();
                    running = true;
                    deltaTime = 0;
                }
                lastTime = bulletClock.getTimeMilliseconds();
                return deltaTime;
            }
        };
    };
    updateRigidbodies = function ()
    {
        bulletWorld.stepSimulation( bulletWorld.clock.getDelta(), 100 );
        ……
    };

Using the current time of The RAF callback like this:

    var lastTime;
    main = function main ( time )
    {
        if ( lastTime === undefined )
        {
            lastTime = time;
        }
        updateRigidbodies( ( time - lastTime ) / 1000 );
        lastTime = time;
        ……
    };
    updateRigidbodies = function ( deltaTime )
    {
        bulletWorld.stepSimulation( deltaTime, 100 );
        ……
    };

@andreasplesch
Copy link
Contributor

Thanks for looking into all these. Could we just use the time in the RAF callback ? This seems simplest unless the Clock class is needed elsewhere.

The lastTime declaration could be moved up into the first var statement. The check for undefined may be avoidable if lastTime is initialized in initScene or so but that does not seem to be worth the trouble.

@microaaron
Copy link
Contributor Author

    var lastTime;
    updateRigidbodies = function ()
    {
        var timeStamp = ( typeof performance === "undefined" ? Date : performance ).now();
        if ( typeof lastTime === "undefined" )
        {
            lastTime = timeStamp;
        }
        bulletWorld.stepSimulation( ( timeStamp - lastTime ) / 1000, 100 );
        lastTime = timeStamp;
        ……
    };

@andreasplesch
Copy link
Contributor

andreasplesch commented Apr 26, 2022

The RAF callback actually gets the current time as an argument.

https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame When multiple callbacks queued by requestAnimationFrame() begin to fire in a single frame, each receives the same timestamp even though time has passed during the computation of every previous callback's workload.

That means the timestamp may not be fresh.

I think that timestamp may be more correct since we are concerned about the delta time between displayed frames, not the exact time the timestepping occurs ?

RAF at time0:
render: takes t_r time
physics: updated to time time0; takes t_p time

RAF at time1 = time0 + 1/60 or possibly 2/60 or 3/60s:
render
physics: updated to time1

This sequence would mean that the rendered physics is one frame behind (but perhaps all the other X3D animation is also one step behind since it is using the same delta time). Ideally, the physics call probably should happen before the render callback but how the callbacks are queued is unclear.

If the physics callback happens first, I think the timestamp would be fresh anyways,

@microaaron
Copy link
Contributor Author

    initScene = function ()
    {
        var collisionConfiguration,
            dispatcher,
            overlappingPairCache,
            solver,
            WorldGravity = new x3dom.fields.SFVec3f();
        collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
        dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration );
        overlappingPairCache = new Ammo.btDbvtBroadphase();
        solver = new Ammo.btSequentialImpulseConstraintSolver();
        bulletWorld = new Ammo.btDiscreteDynamicsWorld( dispatcher, overlappingPairCache, solver, collisionConfiguration );
        bulletWorld.setGravity( new Ammo.btVector3( 0, -9.81, 0 ) );
        //Initialize lastTime. updateRigidbodies should be executed immediately 
        lastTime = timeStamp = ( typeof performance === "undefined" ? Date : performance ).now();
    };
    updateRigidbodies = function ()
    {
        var timeStamp = ( typeof performance === "undefined" ? Date : performance ).now();
        var deltaTime = ( timeStamp - lastTime ) / 1000;
        lastTime = timeStamp;
        bulletWorld.stepSimulation( deltaTime, 100 );
        ……
    };

@microaaron
Copy link
Contributor Author

Not only the callback of RAF, many other tasks may also be in the queue. That makes the timestamp not fresh. So I suggest getting a fresh timestamp when executing stepSimulation.

@microaaron
Copy link
Contributor Author

timestamp0        timestamp1        timestamp2        timestamp3                          timestamp4        timestamp5
    |                 |                 |                 |                                   |                 |
 enqueue           enqueue           enqueue           enqueue                             enqueue           enqueue    
     \                 \                 \                 \                                   \                 \
      \                 \                 \                  -dropped a frame-                  \                 \
       \                 \                 \                                   \                 \                 \
     dequeue           dequeue           dequeue                             dequeue           dequeue           dequeue
        |                 |                 |                                   |                 |                 |   
   deltaTime=0s      deltaTime=1/60s   deltaTime=1/60s                     deltaTime=1/60s   deltaTime=1/30s   deltaTime=1/60s
                                                                            1/30s expected    1/60s expected

@andreasplesch
Copy link
Contributor

In my mind, the timestamp should not be fresh for the Physics. It should be just close to the time a frame is rendered. This way the rendering can properly reflect the Physics. What am I missing ? Did you find a perceivable difference in practice ?

@microaaron
Copy link
Contributor Author

@andreasplesch
Copy link
Contributor

Should all objects drop down at the same time ?

@microaaron
Copy link
Contributor Author

Physics always earlier than graphics rendering. So the timestamp should be fresh.

@microaaron
Copy link
Contributor Author

https://www.youtube.com/watch?v=dXZJdWgwOHk
Delay 454ms. It's to much.

@andreasplesch
Copy link
Contributor

I just thought such a delay may actually be better for a smooth experience since the rendered state does not need to reflect the actual current time but I take your word for it. So let's just use performance.now.

https://developer.mozilla.org/en-US/docs/Web/API/Performance/now#browser_compatibility says performance.now is well supported. Do we still need the fallback to Date.now ?

@microaaron
Copy link
Contributor Author

performance.now is good.
This is for compatibility with older browsers
https://github.com/mrdoob/three.js/blob/dev/src/core/Clock.js#L70
return ( typeof performance === 'undefined' ? Date : performance ).now(); // see #10732

There are two reasons to use Clock.js:
1.If in the future x3dom supports multiple bullet worlds, it will be necessary to assign a clock to each bullet world.
2.It's easy to pause physical motion, just call clock.stop().

@andreasplesch
Copy link
Contributor

mrdoob/three.js#10732 was for node.js support which x3dom does not have.

x3dom probably does not work well any more for browsers older than IE10 in any case.

We could attach a lastTime to each bulletWorld, same as a new Clock is attached but a little cheaper.

clock.stop() would be useful. On the other hand, the RigidBodyPhysics component ( https://www.web3d.org/specifications/X3Dv4Draft/ISO-IEC19775-1v4-CD1/Part01/components/rigidBodyPhysics.html#t-Topics ) does not seem to have a way to stop (or start) time.

So I do not think we really need Clock.js and can keep it simpler.

@microaaron
Copy link
Contributor Author

Maybe x3dom can provide a pause feature earlier than x3d.
Anyway, it is a suggestion for the future.

Now, the improvement should be like this:

( function ()
{
    var CollidableShapes = [],
        JointShapes = [],
        bulletWorld,
        x3dWorld = null,
        initScene,
        main,
        updateRigidbodies,
        MakeUpdateList,
        X3DRigidBodyComponents,
        CreateX3DCollidableShape,
        UpdateTransforms,
        CreateRigidbodies,
        rigidbodies = [],
        mousePickObject,
        mousePos = new x3dom.fields.SFVec3f(),
        drag = false,
        interactiveTransforms = [],
        UpdateRigidbody,
        intervalVar,
        building_constraints = true,
        ParseX3DElement,
        InlineObjectList,
        inline_x3dList = [],
        inlineLoad = false,
        completeJointSetup = false,
        lastTime;
        ……
} )();
    initScene = function ()
    {
        var collisionConfiguration,
            dispatcher,
            overlappingPairCache,
            solver,
            WorldGravity = new x3dom.fields.SFVec3f();
        collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
        dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration );
        overlappingPairCache = new Ammo.btDbvtBroadphase();
        solver = new Ammo.btSequentialImpulseConstraintSolver();
        bulletWorld = new Ammo.btDiscreteDynamicsWorld( dispatcher, overlappingPairCache, solver, collisionConfiguration );
        bulletWorld.setGravity( new Ammo.btVector3( 0, -9.81, 0 ) );
        //Initialize lastTime. updateRigidbodies should be executed immediately
        lastTime = performance.now();
    };
    updateRigidbodies = function ()
    {
        var timeStamp = performance.now();
        var deltaTime = ( timeStamp - lastTime ) / 1000;
        lastTime = timeStamp;
        bulletWorld.stepSimulation( deltaTime, 100 );
        ……
    };

@andreasplesch
Copy link
Contributor

Looks good.

@andreasplesch
Copy link
Contributor

I am not sure but it may be possible to have multiple bulletWorlds using Inlines or Protos since they set up their own scenes. So perhaps saving lastTime per bulletWorld is safer:

        bulletWorld.setGravity( new Ammo.btVector3( 0, -9.81, 0 ) );
        //Initialize lastTime. updateRigidbodies should be executed immediately
        bulletWorld.lastTime = performance.now();
        var deltaTime = ( timeStamp - bulletWorld.lastTime ) / 1000;
        bulletWorld.lastTime = timeStamp;

But it looks like substantial other changes would be also necessary. Getting and setting an object property like this is probably slightly slower than using a scoped variable.

@microaaron
Copy link
Contributor Author

I prefer to put the lastTime into the bulletWorld. It works well.

@andreasplesch
Copy link
Contributor

A bit less code and more future proof as well.

@andreasplesch
Copy link
Contributor

Did you want to update the PR to just use performance.now and bulletWorld.lastTime ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants