pmndrs / cannon-es

💣 A lightweight 3D physics engine written in JavaScript.
https://pmndrs.github.io/cannon-es/
MIT License
1.75k stars 130 forks source link

Discussion : Replace Quaternion with log-quaternion (On Hold) #85

Open d3x0r opened 3 years ago

d3x0r commented 3 years ago

Hello, glad to see some active development on this.... This is the update I did from cannon.js, updated to here (work in progress) https://github.com/d3x0r/cannon.js/tree/d3x0r-lnQuat

Mostly just https://github.com/d3x0r/cannon.js/blob/d3x0r-lnQuat/src/math/lnQuaternion.ts Although Mat3 and Transform have Quaternion intimate knowledge. I added a method setQuat to both quaternion and lnQuaternion classes, which is like a 'copyInto'... for the final conversion between the physics and three.js - requires updating some of the demo code...

To Build with lnQuat

need to copy src/math/ln*.ts to src/math/*.ts imports are hard to replace than requires were

Current build hosted here...

https://d3x0r.github.io/cannon.js/

Todo

marcofugaro commented 3 years ago

Hey, thanks for the suggestion, could you explain a bit more the advantages of the log-quaternion?

d3x0r commented 3 years ago

Well, at the moment, there's none, because I can't get the 'integrate' of rotations to work right;.

Log-quaternions are only 3 coordinates... the so some Q = [x,y,z] angular velocity or curvature More on relating curvature to rotation. For performance, I keep this also as the axis (Q/||Q||) and the angle ||Q||; conversion to quaternion is simply cos(Q.θ/2), sin(Q.θ/2) * axis.

The readme here https://github.com/d3x0r/STFRPhysics There's a section with links to other demos...

The multiplication to rotate a vector (Quaternion.vmult in cannon) is a few less multiplies; though the operation to rotate a rotation (Quaternion.mult) is a little more expensive.

https://d3x0r.github.io/STFRPhysics/3d/indexArmProper.html This rotation explorer has a lot of functionality built in... it's able to show rotation coordinates along with the represented rotations in various ways... I made this video explainer https://www.youtube.com/watch?v=0Y7fmMtlm3Q a while ago; at the time I was convinced the rotation coordinates were also in a 1-norm coordinate space; I subsequently found that it was also a 2-norm coordinate space like regular x/y/z linear coordinates. Angular rotations do not have any particular limits on the range they can represent - there is an effective space of coordinates they end up using between +/- 2pi within x/y/z (something like sqrt(xx+yy+zz)=2pi ). There is no 'normalizing' of log quaternions; every log-quaternion is a valid unit-quaternion.

The Integrate appeared to be (from the indexArmProper explorer) just R = 1/2 At^2 + Vt ... or angular_velocity += angular_acceleation; angular_position += angular velocity. Since any ray from the origin of rotation space defines a rotation axis and the length of the ray determines some rotation; then from that target point, there are also rays in every direction representing the same rotational changes as at the origin; from any point, increasing the Y just serves to rotate the frame around the Y axis. When I used this, the behavior is very similar to what it is now, that it over-accelerates with rotations.

https://d3x0r.github.io/cannon.js/examples/threejs_mousepick.html uses a point-to-point constraint between where you click with the mouse and the cube(s) it intersects with. when left alone, in free space the cube swings faster and faster... (if you start with it rotating fast, it speeds up much faster, can stop it by putting it on the ground... from slow roatations it only adds a little more... ) I then looked at what the equation actually was and realized it's 1/2 w R where wLambda is the velocity change and R is the current orientation... so I changed my equation to this, and this is slightly better (current state), but still does the same thing.

Log-quaternions are also not tied to any 'tick'... a quaternion, without SLERP, can only be applied to rotate one full time step at time... log quaternion can easily scale to any faction of time... https://d3x0r.github.io/STFRPhysics/3d/indexArmProper.html WIth Advanced Options enabled, adds a slider Time Scalar that uniformly changes the 'tick' fraction from '1.0' to +/-3... Inverting the time doesn't really invert the rotation though....

Edit:

I don't know how to swap easily between quaternion and log quaternion with imported modules... right now I copy ln*.ts to *.ts and then build... I was adding a config.json include from the root that becomes CANNON.config ; but that is meant as a static readonly option.

marcofugaro commented 3 years ago

Thanks for the explanation, never heard of log-quaternions. Would be interesting to see where your explorations lead and the advantages they carry.

However for cannon-es, we wanted to continue improving the current Quaternion class and make it more akin to the three.js Quaternion class. That way, since most of the users of cannon.js come from three.js, it's easier for them to use the quaternion interchangeably.

d3x0r commented 3 years ago

well.. I really think such changes should be more like set cannon.Quaternion = THREE.Quaternion and matrix,vec3... but most place just import the types directly instead of the core project to get the quaternion type; but then there's a non-constant lookup done.


So I've been puzzling on how to get an integrate function that works.

So I made this demo, that's able to integrate internal and external angular accelerations (velocities applied to positions) https://d3x0r.github.io/STFRPhysics/3d/indexArmProper-Integration.html ... The relation between frames are drawn with a line, and the long line segments are the net rotation after the integration; the actual path/direction isn't actually relevant, it's just (by default) the arm extends by a segment in the Z direction; under advanced options, can set that as X or Y axis instead (additional controls for other segments are also enabled with advanced). The rotations are set absolutely in angular velocities.

The first segment essentially sets the position. Integration of an angular velocity from 0 is basically a simiple addition; it's when the rotation frame is in a different orientation that things get interesting... so the second (through fifth) sliders apply a angular velocity to the previous ending frame; the analog bends are done in 100 segments; and some 20 of the segments are shown with their full orientation frame at that specified point; other lines indicate the direction of the current angular velocity, the transformed velocity (to world coordinates) and the net axis-axis-axis(log-quat) rotation (in black, dark color and lighter color respectively).

And so... it seems like this would be an appropriate iterative solution; especially when the angular velocity is from internal forces instead of external, yaw/pitch/roll control works relative to the current frame...

The iterative path does look like I would expect it to - with nice graceful arcs from a non-zero rotation to other non-zero rotation points; this also ends up always plotting nice arcs when drawn on a sphere; linear iteration causes unnatural bends at plane crossings (zeros). SLERP way overshoots the proper curve...


But in the end, there's incompatibilities that will be more costly than beneficial at this time.

The angular velocity calculation is the result of a cross product. That means it's really in terms of sin(theta)*N already... and really itself looks like a quaternion as (sort of) 0*cos(theta) + sin(theta)*axis ; which then works right into the quaternion angular integration using (1/2 wR); I was doing some digging looking for derivatives of the Rodrigues rotation formula, but they only provided derivatives for the calculation to rotate a point around an axis, angle; the calculation to rotate axis-angle by axis-angle is slightly different... But then really, it all become mute, because the input factor still has that sin factor on it... and would involve changing internals; somewhere before inertia is applied, it might be converted from sinaxis to angleaxis... but trying that real quick just broke everything worse.

Integration loop... should be something like this?

    Q = current orientation
    A = angular change 

   loop {
       A' = A x Q;  // apply inverse, rotate Q.vmult( A, A', -1 );  if force is internal Q.vmult( A, A', 1 );
       Q' = Q x A';  // update the frame by angular velocity    
       Q = Q`'
   }
d3x0r commented 2 years ago

Hi. I recently was playing with wolfram cloud https://www.wolframcloud.com/obj/67b9ca01-f294-4dcc-a69c-1cff2bf0507c To explore/explain/implement with just math expressions the rotation formula for axis-angle. I ended with a comparison with quaternions, (expecting to be able to show denormalized cases and prove a computational superiority, but failed so...), and figured out I had a cross product backwards; and the difference in the reverse is applying intrinsic rotation vs extrinsic rotation; could be a simple bool to reverse the cross product rather than conjugating and multiplying or reversing the order of operations even. https://d3x0r.github.io/STFRPhysics/3d/index4.html (Use Quaternion option, and Use External Rotation end up being the toggles only for quaternions the quaternions q,r,s in one order are q*r*s and in the other s*(r*q) ; but, taking this back to this swap, and reversing the default, fixed the mouse pick demo.

All my height fields fail for some reason though.

And workers aren't working ( https://d3x0r.github.io/cannon.js/examples/worker.html just 'error' and there is no message...

But; quaternion.integrate() becomes

  integrate (angularVelocity:Vec3, dt:number, angularFactor:Vec3, target = new Quaternion()):Quaternion{
    // with reversed cross product, integration is just partial application.
    return this.mult( new Quaternion().set(  angularVelocity.x *angularFactor.x
                                          ,  angularVelocity.y *angularFactor.y
                                          ,  angularVelocity.z *angularFactor.z ), target, dt ) ;
  }

To and from Euler Angles isn't a used feature; and I don't really have a better conversion; it's sort of easier to just return the x,y and z angles, generally applying them as some yaw pitch roll would also work; but then you'd probably have multiple joints for that that would have just 1 or 2 angles themselves in a fixed order rather than the overall angular change of the frame (axis*angle).

In general usage, this really is best saved as normalized axis and angle; the axis is never de-normalized from the multiplication, so sqrt(xx+yy+zz) is really an expression that only as to be used creating an axis-angle; or after (axis1angle1)+(axis2angle2) (which isn't used either) would have to compute the sqrt of the resulting added vector; but since that sort of thing isn't even used doesn't really matter.

https://github.com/d3x0r/cannon.js/blob/d3x0r-lnQuat/src/math/lnQuaternion.ts#L187 This overall is slightly more work, but the 'dt' parameter is the delta time that integrate can use to apply just part of a rotation, it's a lot of addition mostly; and actually parallel operations (not that any of that helps the JS version). The equivalent vmult() for rotating vectors looks slightly shorter though https://github.com/d3x0r/cannon.js/blob/d3x0r-lnQuat/src/math/lnQuaternion.ts#L290 ... so maybe what's lost in rotating rotations is gained in rotating geometry(?).


Edit: Sometimes just takes me wanting to share, but having things to say 'except blah doesn't work...' figured out the heightfield; bad implementation of quaternion.fromEulerAngles; Then the raycast car was giving me problems, the wheels were sort of flopping in the wrong direction; when I got those right, then the AABB boxes for them were flopping around... not quite sure why I had to reverse the order for these operations; there aren't a lot of demos with child shapes (especially that move independent from the parent, but relative to the parent like wheels)

https://d3x0r.github.io/cannon.js/ Everything (except workers for some reason) works. (my fork started as a fork of cannon.js but I also recently merged cannon-es head)