DualQuaternion implementation and YawPitchRoll and euler addition to Quaternion.#60
DualQuaternion implementation and YawPitchRoll and euler addition to Quaternion.#60nanjizal wants to merge 13 commits intotbrosman:masterfrom
Conversation
|
I skimmed through it, looks like it covers the basics. I like it. I'll review more thoroughly when I get more free time. Can you add some tests around transformations? I have a few you can adapt: https://github.com/tbrosman/hxmath/blob/master/test/Test3D.hx It might also be good to create a few comparison tests vs. Frame3 to verify that it is equivalent. |
|
I have added a test and some code to test eval and cppia target. Confused it seems to pass on some targets not others?! + neko pass
+ cppia pass
- cpp fail
- eval fail
! hashlink untested |
|
Not sure invert is the ideal test... since that method is not currently in my version, so I am not totally sure on that specific method. I started trying to add a js version, not sure the tests are running correctly for neko and cppia, this is my first time with this test framework. |
|
I think it is definitely failing this test :( , but I would like to understand why neko/cppia do not seem to apply testing properly. |
|
perhaps I have setup the matrix incorrectly since I use a 4x3 matrix with different labels, time to find some food. edit: Quick check modifying matrix still failed. need to dig deeper. |
- Invert formula was wrong - Multiplication re-ordered terms (quaternion multiplication is not commutative) - addWith subtracted Also added more tests.
|
I ran Hashlink, cppia, and eval. I got the same failures on all three targets. I think your hxml targets might not be running stuff correctly. I fixed the math issues and opened a PR into your fork: nanjizal#1 (Bonus: in the process I found a case where Frame3's internal Matrix can be modified directly. I'll open a bug for that.) |
Fixes for DualQuaternion
|
Thomas DON'T PULL THIS YET, I THINK YOUR MODIFICATION IS NOT GOING TO WORK My DualQuaternion implementation is based on this paper, your changes don't seem to agree with the paper. The multiplication within the paper is defined: Haxe implementation @:op(A * B) public static inline
function multiplyQ( q1: DualQuaternion, q2: DualQuaternion ):DualQuaternion {
return new DualQuaternion({ real: q2.real * q1.real
, dual: q2.dual * q1.real + q2.real * q1.dual });
}This is very important since with the amends you made, when applied to my library geom breaks or rather disables the 3D translation in my trilateral2 tests, ie applying any translation has no effect ( instead it seems to scale it in the z-axis on initial load, I checked my original code against the paper and there was an error last two terms swapped over but did not seem to matter to translation. I have updated geom with the function above and not changed getTranslation code yet. [ I am not sure if it's relevant but my geom library uses real matrix positions in normal maths, and transposes the transform for the WebGL shaders only at the end just before sending to shader, are you doing it this way, lots of online stuff is a little confusing in this regard, are you doing it this way? ] To apply keyboard translations and rotations my code currently uses Axis3 effectively it is doing I can have a go at setting up a WebGL demo with hxmath ( safer for a test than using OpenFL because you can be more sure of internal details from start to finish ) to try to double check physical results. But before doing so, as it can often be fiddly, I thought I should check in and get your thoughts, probably I should have mentioned the paper earlier, I think your likely much better at the maths and will appreciate the paper. Prior to consideration on inverse it's important to resolve multiplication definition, I feel the one above is suitable - agreeing with the theoretical paper and seems to work practically with my WebGL experiments? Best Justin |
|
I'm not sure why their code swaps the order of multiplication. Section 5.2 of the same paper actually defines multiplication correctly. Since dual quaternion multiplication is non-commutative (like regular quaternions), the result would be different. (p + e q) (r + e s) = p r + e (p s + q r) Quaternion multiplication for p = [s, u] and q = [t, v] can be written using mixed vectors and scalars: [s, u][t, v] = [s t - u * v, s v + t u + u X v] (where * is a dot product and X is a cross product). Since u X v = - u X v, the quaternion product does not commute. It might be that their multiplication is like "concat" and not really multiply, and then everything was multiplied in reverse. Kind of weird. This is an even further reach, but it might have been done intentionally to match up with matrix multiplication order. |
|
This paper seems to have: The original paper is the paper is the one the Luxe engine used. |
|
Just reading Christian Rau points here: |
|
I created a test comparing my Frame implementation to DualQuaternion and a short constructive walkthrough: https://gist.github.com/tbrosman/43c451f10cddd0826353ccf8af716898 They are equivalent in the current implementation. Test: public function testDualQuaternionMult_ConsistentWithFrame3()
{
var dualQuatA = DualQuaternion.fromAxisAngle(90, Vector3.yAxis, new Vector4(10, 0, 0, 1));
var dualQuatB = DualQuaternion.fromAxisAngle(90, Vector3.xAxis, new Vector4(0, 0, 5, 1));
var dualQuatC = dualQuatA * dualQuatB;
var frameA = new Frame3(new Vector3(10, 0, 0), Quaternion.fromAxisAngle(90, Vector3.yAxis));
var frameB = new Frame3(new Vector3(0, 0, 5), Quaternion.fromAxisAngle(90, Vector3.xAxis));
var frameC = frameA.concat(frameB);
var dualQuatCRotation = dualQuatC.getRotationQuat();
// TODO: Make getTranslation return a Vector3
var dualQuatCTranslation4 = dualQuatC.getTranslation();
var dualQuatCTranslation = new Vector3(dualQuatCTranslation4.x, dualQuatCTranslation4.y, dualQuatCTranslation4.z);
var frameCRotation = frameC.orientation;
var frameCTranslation = frameC.offset;
assertApproxEquals(0.0, (dualQuatCRotation - frameCRotation).length);
assertApproxEquals(0.0, (dualQuatCTranslation - frameCTranslation).length);
}The flipped multiplication just seems weird. An example of why it is weird: say you have two dual quaternions with only a rotation (labeled "real" currently) portion. What happens when you concatenate them? In the code you provided: This is equivalent to some operator (X) that acts like this: Which means in a pure rotation case (q = 0, s = 0) you would have r * p. Since these are quaternions, r * p != p * r and (X) would give results that were inconsistent with just multiplying two regular quaternions (even though that's what the inputs were). |
|
Thomas I'm probably out of my depth I have posted a link to multiplication in geom and in hxmath perhaps they will comment further. Here is snipit from conversation: Nanjizal> chakravala> mewertx0rz> |
|
I think I can put some geometric intuition behind the non-commutativity. A dual quaternion can be thought of as a map that takes some point v and then applies (in order) rotation and then translation. In general, rigid body transformations do not commute. If you rotate a point 90 degrees counter-clockwise around the X axis and then add (0, 10, 0) to it the result is not the same as adding (10, 0, 0) first and then rotating. Just rotation: Just translation: Translation, then rotation: Rotation, then translation: |
|
Ok cool, I did a lot more research your approach seems the best option, sorry for my caution. I have not yet created a visual test, I have been fighting with general setting up WebGL shaders in OpenFL, but if your happy to accept pull then go for it. I will look to add the 'complex number class' soon. It is likely a lot more robust as it has quite a few tests. |
hxmath/math/DualQuaternion.hx
Outdated
| * | ||
| * @return The modified object. | ||
| */ | ||
| public inline function applyConjugate():DualQuaternion |
There was a problem hiding this comment.
Might be good to skip this operation. There are two conjugates for DualQuaternion: the dual conjugate and the complex conjugate.
hxmath/math/DualQuaternion.hx
Outdated
| * @return ~a | ||
| */ | ||
| @:op(~A) | ||
| public static inline function conjugate(a: DualQuaternion):DualQuaternion |
There was a problem hiding this comment.
See comment below. Should either be skipped, or the documentation should make it clear this is the complex conjugate. I could also see this being implemented as the complex + dual conjugate. This is the way transformation by a dual quaternion is implemented: q * v * complexConjugate(dualConjugate(q)).
There was a problem hiding this comment.
I think you have lost me here.
hxmath/math/DualQuaternion.hx
Outdated
| * @param z | ||
| * @return self | ||
| */ | ||
| public inline function setReal(s:Float, x:Float, y:Float, z:Float):DualQuaternion |
There was a problem hiding this comment.
Not sure these helpers are useful. The fields are forwarded, so probably easier to set it that way.
hxmath/math/Quaternion.hx
Outdated
| * but set /gets internally as the Quaternion value | ||
| * | ||
| **/ | ||
| public var euler(get, set): Quaternion; |
There was a problem hiding this comment.
I wouldn't expect this as a Quaternion. Either needs its own structure, or you could use Vector3.
There was a problem hiding this comment.
Added Euler abstract over Vector3
test_eval.hxml
Outdated
| -lib nanotest | ||
| -dce full | ||
| -cmd mkdir -p bin/eval | ||
| #-cmd mv test.cppia bin/cppia/test.cppia |
hxmath/math/DualQuaternion.hx
Outdated
| * | ||
| * @return The rotational Quaternion | ||
| */ | ||
| public inline function getRotationQuat(): Quaternion |
|
Cool, I'll try to add more documentation/references when I get time to work on this again. This is one of the more useful papers I found while refreshing my brain: https://www.cs.utah.edu/~ladislav/kavan06dual/kavan06dual.pdf It goes over how to set up the product for transforming points. In the meantime there are a few things this PR needs:
One more thing I keep forgetting: if I include your name in contributors, haxelib associates the library with your username. It is easier for me to just credit you in the CHANGELOG.md (and class file). I found this out the hard way a year ago when someone updated my Haxe REST Client library and I tried to submit the change. Basically haxelib doesn't differentiate contributors and owners: https://lib.haxe.org/documentation/faq/ |
nanjizal
left a comment
There was a problem hiding this comment.
I have adjusted the Quaternion stuff, it should be tested but internally the code agrees with Wikipedia.
I have left the new 'applyDualConjugate' method with a throw not implemented please advise.
Testing not yet addressed.
|
I will look at testing hopefully later today. |
|
I have not added the Tests you asked for yet, sorry but I have instead implemented ( ported ) a Complex class. The Complex has coverage for unit tests ( for most aspects ). Best Justin |
|
Can you make the Complex class a separate PR? I think it's out of the scope of this change. Let me know if you need any suggestions/ideas for the tests. |
|
I have moved the code into two separate branches. Unfortunately it seems simplest to close this pull the Dual code is now in this pull. |
|
Note that so far have only separated them. They are both WIP. |
Hi Thomas
I have implemented a DualQuaternion based on my version but following where I can your Quaternion api design and your code style, I added the import to the test so it compiles without errors on neko.
My geom.matrix.DualQuaternion is used for Keyboard rotation translation here:
https://nanjizal.github.io/trilateral2Setup/binWebGL/index.html ( arrows keys, tab, shift, return, delete, space... ).
I believe the code should work as it's very similar to mine. But not yet implemented a visual test, or added any unit tests yet, I normally use DualQuaternion.create and pass the Quaternion real in with YawPitchRoll so I have added that to your Quaternion class, but you can use Axis approach like this because that uses your previous Quaternion code:
Basic use
( I have am not too sure if slerp or lerp will work, they need to be tried if they do work that might be fun.)
Generally you can build IK type structures quite easily with DualQuaternions just by multiplying them in any order to build rotation and translations like
So you can get the position of a finger tip by setting up the positions:
I am unsure on details of setting up constraints, it's quite hard to find lots of good details on DualQuaternion especially ones that don't get too technical, but they are used for robot arms.
To get the Quaternion rotation out you just call dq0.real, I have added a clone for that to increase clarity for users. To get the Vector4 out you can use getTranslation.
Added my name to haxelib authors file but feel free to remove, and I left a comment at top of the DualQuaternion class, but that can be removed ( especially when I get code role, not working in code at moment ).
Hope the code looks good it took awhile, so can't promise I will look at porting the Complex numbers implementation soon, although that is much better tested.
Best Justin