chandlerprall / Physijs

Physics plugin for Three.js
MIT License
2.77k stars 455 forks source link

applyForce() #19

Closed rscottfree closed 12 years ago

rscottfree commented 12 years ago

The short version: I am accelerating an object using applyImpulse() and then trying to slow it down by turning the object around and accelerating again, but it just makes it go faster in whichever direction it was going. I'm wondering if something like applyForce() would be more appropriate and not result in strange behavior like this and if applyForce() could be put into Physijs.

The code:

// applying the impulse for forward acceleration
ship.applyCentralImpulse(ship.matrix.multiplyVector3(new THREE.Vector3(0, 10, 0)));

// rotate the ship clockwise
ship.applyImpulse(
                ship.matrix.multiplyVector3(new THREE.Vector3(10, 0, 0)),
                ship.matrix.multiplyVector3(new THREE.Vector3(0, 1, 0))
);
ship.applyImpulse(
                ship.matrix.multiplyVector3(new THREE.Vector3(-10, 0, 0)),
                ship.matrix.multiplyVector3(new THREE.Vector3(0, -1, 0))
);

Some background: I'm working on a scene where I have a space ship. I'm using applyCentralImpulse() currently to apply a force to this ship every 100 milleseconds when the spacebar is pressed to simulate a rocket engine propelling the space ship forward. I have set gravity to Vector3(0, 0, 0), and I have made controls to turn the space ship using two applyImpulse() commands with a small offset to rotate the ship. This is all in the x, y, and I'm not worrying about z (other than setting the ships z position to always be 0).

Everything works as expected until the ship get's going too fast (in my case about 1000 in the Y direction). When I try to turn the ship around and applyCentralImpulse() now in the -Y direction, it just accelerates more in the Y direction. If the ship is going slow enough I can turn it in any direction and get it to slow down.

Reading through the documentation for ammo.js and specifically the Bullet physics engine, it seems like what I should be doing is an applyForce(). I'm wondering if applyImpulse is actually the wrong thing for me to do in this scenario and because of something about that function that I don't understand, I'm seeing really strange results.

(I also notice that when I rotate the ship while going fast, the rotation actually makes the ship go even faster while also rotating it.)

Could applyForce() be implemented in Physijs? It seemed a little bit different than applyImpulse, which is a one-time thing, and that with applyForce you had to be more connected with the time-cycle of the physics simulation, and give a time over which to apply the force--so maybe that makes it more difficult to easily port to be used within Three.js.

Anyway.... I'd really like to find a solution to this and can't seem to do so with the current tools and my understanding.

(Btw--I've also tried just multiplying the the ship's position vector by a scalar to simulate acceleration, but that has funny results as well.)

chandlerprall commented 12 years ago

I have added applyForce and applyCentralForce. The difference between an impulse and a force, like you stated, is the timing. An impulse is applied in full all at once while a force is applied over time. Gravity is a force, it's applied steadily over the life of an object, but shoving something is an impulse.

Best way to use the new force methods is to add an event listener to the scene for the update event:

var scene = new Physijs.Scene;
scene.addEventListener( 'update', myUpdateHandler );

Apply the force again each time myUpdateHandler is called. You can ensure only one time step is simulated at a time by calling scene.simulate( undefined, 1 ); - the first argument is the amount of time to simulate, undefined tells Physijs to attempt to simulate all time since the last call. The second argument limits the number of steps to simulate to just 1, which will cap the simulated time at the time step (default is 1/60 or 16.7ms). This will make sure myUpdateHandler is called after every frame so you can apply the force again.

I will have to set up a test to try and duplicate what you are seeing with the rocket speeding up endlessly, I don't know what is causing that. In the mean time, let me know how using force works for you.

rscottfree commented 12 years ago

YES! applyForce() and applyCentralForce() work as expected. Thank you.

As for the issue with applyImpulse() and it speeding up: my latest theory is that when I'm applying the impulse over and over again, I'm actually applying the impulse faster than the simulation can take it in, and when I turn the object around and accelerate in the opposite direction, it applies all the impulses that it didn't have a chance to apply before, causing the object to speed up in the same direction it was heading instead of slowing.

Not knowing the math or code very well, I'm just grasping at straws. Either way, applyForce() works predictably and I'm quite happy with it. Thanks again.

rscottfree commented 12 years ago

I figured out the issue of ever-accelerating objects. It had to do with the way I was converting the force vector into the the right rotation to apply to my object (to be applied in the XYZ of the object instead of the XYZ of the world). This is the link to my initial question about this

Instead of doing this: ship.applyCentralForce(ship.matrix.multiplyVector3(new THREE.Vector3(0, 10, 0)));

You have to do something like this:

var rotation_matrix = new THREE.Matrix4();
rotation_matrix.extractRotation(ship.matrix);
var force_vector = new THREE.Vector3(0, 100, 0);
var final_force_vector = rotation_matrix.multiplyVector3(force_vector);
ship.applyCentralForce(final_force_force);

What I was noticing before was the farther my ship object got away from origin (0, 0, 0), the more erratic it seemed to behave when I applied forces or impulses to it. I realized that when I was multiplying the matrix by my force vector, it was probably multiplying it by the position part of the matrix as well, which I didn't want. I just wanted to convert the force's rotation to be relative to the rotation of the object.

There might be an easier way to do this still, but for now it is working quite well. Also, applyCentralForce() seems to have better results than applyCentralImpulse() when dealing with continual application of forces (like a rocket ship firing).

lazd commented 11 years ago

Here's a version of @rscottfree's method that works with three.js r55+:

var rotation_matrix = new THREE.Matrix4().extractRotation(ship.matrix);
var force_vector = new THREE.Vector3(0, 100, 0).applyMatrix4(rotation_matrix);
ship.applyCentralImpulse(force_vector);