Okay, it's done (for the most part). Look at revision 38 on the BrawlTools SVN.
http://code.google.com/p/brawltools/source/detail?r=38
I have also built a preview release so you can test it out:
http://brawltools.googlecode.com/files/BrawlBox%20v0.64%20Preview.zip
Lots of changes yes, but our focus is on:
Maths / Matrix / Vector3
GLPanel / GLContext / ModelEditControl
All the work takes place in ModelEditControl:
Take note of the PostRender event, which renders the rotation orb when a bone node is selected.
- At the very end of PostRender, ghost nodes are drawn into the z-buffer for every bone. The z-buffer is cleared prior.
- New display list objects are employed through GLContext.GetSphereList / GetRingList / GetCircleList. These all contain primitives with a radius of 1. Rendering to the desired radius is simply a matter of calling glScale with the desired radius.
Take note of the MouseDown event, which handles bone node targeting.
- First, the currently-selected bone is tested against the mouse point.
- Take note of how the 'radius' variable is calculated, scaling the orbRadius constant to the camera distance.
- The GLPanel.ProjectCameraSphere method is called for the current bone. This either intersects the bone sphere or its camera-facing plane using a ray from the mouse point.
- This point's distance is then compared to the bone node, and tested against the radius and the orbit ring.
- If the point falls within radius: convert the point to local-space, get its angles, and test for axis snapping.
- If the point falls within the orbit ring, enable orbit snapping.
- If the point falls within neither, transfer to hit-detection. (We will try selecting a new node)
- After snapping flags have been set, call GetOrbPoint. This retrieves the world-point that intersects our snapping plane, using a ray from the mouse point. Take note of how the normals for the planes are calculated. The point is then clamped to a distance of 'radius' from the node center.
- Convert this point to local-space and store it. This will be our origin point.
- Store the bone's old rotation values. These will be used for escape-key cancelling.
Part 2: If no bone is selected use hit-detection
- Get depth of mouse point. If < 1 we found something.
- UnProject the point to world coordinates.
- Compare all bones to this point, finding one whose distance matches the node radius. (Not the rotation orb)
The MouseMove event is where the rotations are updated:
- Call GetOrbPoint to retrieve the snapped world-point.
- Convert the point to local-space. We will compare this to our origin point.
- Create a matrix by multiplying the bone's transformation to an Axis/Angle matrix.
- The Axis/Angle matrix is created by Matrix.AxisAngleMatrix, just supply the origin point and the new point.
- With this new matrix, derive the resulting angles by calling GetAngles.
- Subtract the bone's frame angles from the resulting angles to get the difference. Notice how GetAngles is used twice.
- Truncate the difference by wrapping the degrees from >180 and <-180. This allows the angles to continually increase/decrease.
- Apply the new angles by adding the difference to the bone's frame state. (I let the anim panel handle this so that keyframes are created)
Some things to watch out for:
Bone scaling may affect how these points are converted to local-space.
I ditched the Quaternion method because conversion is a nightmare. However, converting a matrix to euler angles can be problematic. You may notice that the rotations look fine, but the angles are odd. This particularly happens to the y-angle.