13.1.2 Transform3D
The Transform3D class represents a generalized geometric transform that is represented
internally by a 4x4 matrix. The transform method applies the
transformation to a data point or vector, in essence transforming the entity from one
coordinate system to another. The other methods provided by Transform3D allow
access to the state of the transformation in many different ways, both as a matrix and
as individual transformation components— translation, rotation, and scale. As a
matrix, transformations can be added, multiplied, normalized, transposed, and
inverted. As transformation components, translation, rotation, and scaling can be set
individually and in combination. Most of the methods also come in different versions
for different parameter types— float and double.
There is some symmetry and consistency among the methods, but they are far
from complete. Some of the transformation component methods affect only a portion
of the transformation, preserving the rest of it. Others reset the complete transformation
and then set the component. For example, setRotation(AxisAngle4d) and
setRotation(Quat4d) preserve the original non-rotational portions of the transformation,
but setEuler(Vector3d), which sets the transform's rotation using
Euler angles, does not. And, although there are methods for setting the rotation as an
axis-angle, a quaternion, or as Euler angles, the only get method for rotation is for a
quaternion. (Note that AxisAngle4d provides a set method for Quat4d, which in
a round-about way allows the rotation component of a Transform3D to be converted
into an AxisAngle4d.) `
Matrix multiplication
Multiplying transforms together concatenates and combines their individual effects.
This offers an alternative to nesting transform nodes in order to achieve complex
transformations. For example, to achieve the 180-degree rotation described in the
previous section, the two transforms with 90-degree rotations could be multiplied
together and the resulting transform applied to the bottommost shape. The order in
which the transforms are multiplied is significant. To reproduce the effect of nested
transforms in a scene graph, start with the topmost transform, multiply it by the
transform corresponding to the next lower transform in the graph, and repeat the
process while accumulating the transform until the bottommost transform is reached.
In general, using nested transforms is easier to conceive but multiplying transforms is
often easier to implement because fewer scene graph elements are involved.
A situation where transform multiplication is superior to nested transforms is
incremental updates to an object's geometry. For example, if an existing object in the
scene graph needs to be rotated 90 degrees relative to its last transformation— rotation,
translation, or scale— having nesting transforms wouldn't help. Instead, the transform
in the transform group that performed the operation could be read, multiplied with
a transform representing a 90-degree rotation, and the result written back to the transform
group. Both the SMTransformGroupPlugin and MMTransformGroupPlugin
classes in the framework's j3dui.control.actuators package use this form of
incremental update.
Access by value
Access to the Transform3D component object in a TransformGroup is strictly by
value and not by reference. When the transform in a TransformGroup object is set or
gotten, the Transform3D value is copied instead of the reference to the value being
transferred. This means that each time you want to change an object's position or
rotation you have to create a new Transform3D object or modify a previously created
one, and then explicitly set it in the target TransformGroup; you can't simply associate
the two objects and then just update the Transform3D. This can be an inconvenience
at times, and beginners can easily forget to do it.
Point versus vector
There are quite a few transform methods associated with Transform3D, but the
main distinction among them is between transforming points and transforming vectors.
When a point is transformed, in essence, the full transformation is applied, with
the position of the point being re-interpreted in a different and possibly translated,
rotated, and scaled space. When a vector is transformed, the transformation is interpreted
differently: Only rotation and scale are applied because translation of a vector,
which by definition defines only direction and magnitude, doesn't mean a whole lot.
The distinction between these two forms of transformation, which is actually defined
by the data being transformed and not the transformation itself, can be subtle but
important. Transforming a ray is a different matter, but a ray isn't a primitive element
defined by the javax.vecmath package or handled directly by Transform3D.
13.1.3 getLocalToVworld
Whether nested in the scene graph or concatenated through multiplication, the composite
transformation of a node can sometimes be difficult to track, especially if
changes to the transform chain in the scene graph can be made by different parts of
the application at the same time. To address this problem, Java 3D imbues all scene
graph nodes with the ability to divulge their absolute geometry in the virtual world.
The getLocalToVworld method of the Node class allows you to obtain a
Transform3D defining how to convert from the node's local space to that of the virtual
world, which works no matter how nested or concatenated an object's transformation
is. Note that for a transform group node, its getLocalToVworld method
returns the transform for the space in which the node itself lives, not that of its children.
In other words, the transform state of a TransformGroup node has no effect on
the transform returned by its getLocalToVworld method.
The local-to-Vworld transform has many uses. It can be used to determine the
absolute world position of a shape object's vertex, or to determine the direction of for-ward
in the world relative to a view object. For example, in first-person navigation,
movement of the user's view occurs relative to the view. To correctly move the view
in the world, you have to be able to interpret local view-relative control inputs such
as forward, right, and left, into absolute movements in the world space. To move the
view forward by 10 meters you would start with a 10-meter long vector pointing
straight ahead in the view's local coordinate system, which would correspond to a vector
of (0, 0, 10). Using the ViewPlatform object's local-to-Vworld transform, you
would translate this locally defined vector into an absolute world direction and magnitude.
Multiplying the transform defining the world position of the view by the transformed
vector concatenates the two transforms into one, which has the effect of
moving the view forward by 10 meters relative to the view's current position. This
example is illustrated in the following code fragment:
// create the world space
VirtualUniverse universe = new VirtualUniverse();
Locale locale = new Locale(universe);
BranchGroup root = new BranchGroup();
locale. addBranchGraph(root);
// create the view space
ViewPlatform view = new ViewPlatform();
...
// build a view actuator
/// create a view actuator and add the view to it
TransformGroup actuator = new TransformGroup();
actuator.addChild(view);
/// add the view actuator to the world's root node
root.addChild(actuator);
// arbitrarily manipulate the view using its actuator
...
// move the view forward relative to itself by 10 meters
Vector3d forward = new Vector3d(0, 0, -10);
/// get the view's local transform
Transform3D xform = new Transform3D();
view. getLocalToVworld(xform);
/// transform the forward vector from local to world space
xform. transform(forward);
/// get the current view actuator state
Transform3D current = new Transform3D();
actuator.getTransform(current);
/// apply the change vector to the view actuator state
Transform3D change = new Transform3D();
change. set(forward);
current.mul(change);
/// don't forget to set the new transform
actuator.setTransform(current);
...
In case you are wondering what the difference is between the // and /// comments,
this is a convention used to indicate levels or nesting of comments and associated
code functionality. If you read the top-level // comments and skip the code and
lower-level comments you should get a pretty good overview of what is going on. The
lower-level /// comments within a given top-level comment section provide details
about the code and its functionality.
New on the Java Boutique:
New Review:
Time Management Made Easy with the Quartz Enterprise Job Scheduler
Why not just use the Java timer API? This open source scheduling
API boasts simplicity, ease-of-integration, a well-rounded feature
set, and it's free!
New Applet:
Reverse Complement
Reverse Complement is a simple applet that converts DNA or RNA
sequences into three useful formats.
Elsewhere on internet.com:
WebDeveloper Java
Lots of Java information on webdeveloper.com
WDVL Java
Thorough Java resource at the Web Developer's Virtual Library.
ScriptSearch Java
Hundreds of free Java code files to download.
jGuru: Your View of the Java Universe
Customizable portal with online training, FAQs, regular news updates, and tutorials.
|