G3dControl Camera Model
The ``camera transformation,'' as defined in G3dView.WorldToViewMatrix, is
[x y z 1] [Wi] [S] [T] [R] [X] [Ei] [P]
where
Wi is the incremental world transform, if any,
S is scale,
T is translate,
R is rotate,
X swaps y and z (world space is right-handed, eye and view spaces are left-handed),
Ei is incremental eye transform, if any,
P is perspective.
[Artwork node; type 'Artwork on' to command tool]
The standard parameters are in G3dControl.Camera.par, namely:
1 scale (no differential scale)
3 mov (x, y, z translates)
3 rot (x, y, z); R = Rx.Ry.Rz, in that order
1 field of view
For a more intuitive interaction, incremental controls are provided that insert, one at a time, simple translation or rotation transformations into the transformation pipeline in world space or eye space. These are camera.wor and camera.eye.
In order to maintain a unique, nonredundant, repeatable camera representation, these incremental controls are ``absorbed'' or merged back into the standard parameters.
Incremental World Space Transforms
Note: Mi means M incremental.
World Space Translate
Given Wi = Ti = a translation,
find T', R' such that TiSTR = ST'R'
where | | is the matrix symbol
| |
and t and ti are 1X3 = [x y z] matrices,
and s and r are 3X3 matrices
TiSTR = |1 0| |s 0| |1 0| |r 0|
|ti 1| |0 1| |t 1| |0 1|
combining the first two terms:
= |s 0| |1 0| |r 0|
|sti 1| |t 1| |0 1|
expanding the first term:
= |s 0| |1 0| |1 0| |r 0|
|0 1| |sti 1| |t 1| |0 1|
combining the second and third term:
= |s 0| |1 0| |r 0|
|0 1| |t+sti 1| |0 1|
= S T' R'; thus, T is modified, R is unmodified
World Space Rotate
Given Wi = Ri = a rotation,
find T', R' such that RiSTR = ST'R'
RiSTR = |ri 0| |s 0| |1 0| |r 0|
|0 1| |0 1| |t 1 ||0 1|
combining the first two terms:
= |ris 0| |1 0| |r 0|
|0 1| |t 1| |0 1|
manipulating the first two terms:
= |s 0| |ri 0| |r 0|
|0 1| |t 1| |0 1|
manipulating the second and third terms:
= |s 0| |1 0| |rir 0|, (because tr = tri-1rir)
|0 1| |tri-1 1| |0 1|
= S T' R'; where R'=RiR; thus, T is modified, R is modified
A new rotation matrix R' is computed, then its rotation parameters are extracted.
Incremental Eye Space Transforms
Eye Space Translate (dolly: left/right, up/down, in/out)
Given Ei = Ti = a translation,
we want to find T', R' such that TRXTi = T'R'X
where X = swap matrix: |1 0 0 0|
|0 0 1 0|
|0 1 0 0|
|0 0 0 1|
TRXTi = |1 0| |r 0| |x 0| |1 0| |x 0| |x 0|, (two swaps okay, since XX = I)
|t 1| |0 1| |t 1| |ti 1| |0 1| |0 1|
combining the third, fourth, and fifth terms:
= |1 0| |r 0| |1 0| |x 0|, where tx = (a, c, b) if ti = (a, b, c),
|t 1| |0 1| |tx 1| |0 0|, since Tx = XTiX = |1 0|, and x = |1 0 0|
|tix 1|, |0 0 1|
|0 1 0|
manipulating the second and third terms:
= |1 0| |1 0| |r 0| |x 0|
|t 1| |txr-1 1| |0 1| |0 1|
combining the first two terms:
= |1 0| |r 0| |x 0|
|t+txr-1 1| |0 1| |0 1|
= T' R' X; thus, T is modified, R is unmodified
Eye Space Rotate (Gimbal: picthc, yaw, roll)
Given Ei = Ri = a rotation,
find T', R' such that TRXRi = T'R'X
TRXRi = |1 0| |r 0| |x 0| |ri 0| |x 0| |x 0|, (two swaps okay, since XX = I)
|t 1| |0 1| |0 1| |0 1| |0 1| |0 1|
combining third, fourth, and fifth terms:
= |1 0| |r 0| |rx 0| |x 0|
|t 1| |0 1| |0 1| |0 1|, where Rx = XRiX,
rx = xrix = |1 0 0| |a b c| |1 0 0| = |a c b|
|0 0 1| |d e f| |0 0 1| |d e f|
|0 1 0| |g h i| |0 1 0| |g h i|
combining the second and third terms:
= |1 0| |rrx 0| |x 0|
|t 1| |0 1| |0 1|
= T' R' X; where R'=RRx= RXRiX; thus, T is unmodified, R is modified
An extraction is needed.
— Paul Heckbert
G3dQuaternion
In the code, both the incremental rotation and the aggregate rotation are represented as unit quaternions, qInc and qAgg. Just to remind you, a unit quaternion has four components, [x, y, z, w], naturally grouped as a vector v=[x, y, z] and a scalar w, with x2+y2+z2+w2=1. The mapping from unit quaternions to rotations is such that [v, w]=[u sin q/2, cos q/2], where u is a unit vector along the axis of rotation and q is the angle of rotation. Quaternion multiplication maps to rotation composition. To end these preliminaries, note that since sine is an odd function, the inverse of unit q=[v, w] is its conjugate, q*=[—v, w].
The quaternion for the rotation determined by the arc from p0 to p1 on the unit sphere is given by p1p0*, where the product is the quaternion product, here equal to [p0 p1, p0 . p1]. It's pretty easy to see that this gives a rotation about the axis perpendicular to the plane of the arc, by an angle equal to twice the arc length. (The dot product gives cosine of the arc length, and the cross product gives a vector perpendicular to the plane of p0 and p1 with magnitude the sine of the arc length.)
The angle doubling is important for two reasons. First, it allows all possible rotations, from 0° to 360°, about any axis, to be indicated by an arc on the visible hemisphere. Second—and more subtle—it works out so that the rotation for p1 from p0 followed by the rotation for p2 from p1 is equal to the rotation for p2 from p0. (This is trivial to show using quaternions. Let q10=p1p0*, q21=p2p1*, and q20=p2p0*. Then q20=q21q10=p2p1*p1p0*=p2p0*, since p1*=p1-1.) This has the important implication that as one drags the end of an arc, the change in orientation is natural, since a dragging motion acts like an incremental arc with the same effect as a final arc, and the spherical "vector addition" works. Also, the present orientation is not affected by the path I took to bring the end to its present location; not so for an ordinary trackball.
This whole idea grew out of the fact that there is an isomorphism between rotations and arcs on a sphere, (see D. S. Richardson's paper "Quaternion Algebra for Three-Dimensional Computer Graphics and Modelling", Australian Comp. Sci. Communications, 5(1), 109—120, February 1983; Richardson makes the error, throughout his paper, of omitting angle doubling.) While I had known of this for quite some time, I never saw any particular use for it, until inspiration hit a week ago.
Note that the isomorphism does allow us to plot a rotation/orientation in a graphically meaningful way, not just an input one.
If the increment is left multiplied times the aggregate, qIncqAgg, the axes will remain fixed with respect to the viewer; this seems the least confusing way to do it. However, if we right multiply, qAggqInc, the axes will remain fixed with respect to the object, which is sometimes handy.
The arcs should increment, not just set, the current orientation, because they would always start as zero length; a non-incremental effect would be too jarring. And so that the user could get an idea of the axis by looking at the feedback on the ball, we draw the projected arcs, rather than the simpler chords.
In summary, a few simple algebraic ideas:
(1) Stick to unit quaternions: x2+y2+z2+w2 = 1
(2) Quaternions have four real components: [x, y, z, w],
which can be treated as [vector, scalar]: [[x, y, z], w]
(3) Quaternions are almost an axis-angle description:
[v, w] = [UnitAxis*sin Angle/2, cos Angle/2]
(4) The composition of rotation q1 followed by q2 is the non-commutative product: q2 q1
And a few geometric ideas for unit quaternions:
(5) They form a unit sphere in x-y-z-w 4D space
(6) Diametrically opposite points are the same rotation
(7) Travel along a great arc gives rotation about a fixed axis
(8) Arc length is half the rotation angle
Lastly, a meta-rule:
(*) Quaternions are like complex numbers, but multiplication doesn't commute.
— Ken Shoemake
G3dNormalCoding
The rationale behind G3dNormalCoding
G3dNormalCoding allows the conversion of normalized vectors to or from 8-bit integers. This is useful for interactive tools that perform shading via the colormap; each entry in the colormap corresponding to a particular normal. Once each colormap entry is filled with the shade for its associated normal, the frame buffer image will appear shaded.
The rationale behind the design
This approach has its drawbacks. First, Dorado frame buffers, as do most frame buffers, allow only 256 entries in the colormap. Thus, each picture may display only 256 different normals. Second, there is a necessary loss of precision when converting from an encoded (8-bit) normal to a full floating-point normal.
The impact of the first problem is reduced by dithering the normals before encoding; each normal has a small amount of noise added to it before it is quantized into one of the 256 buckets. There's not much to be done about the second problem. When reading a normal back from the frame buffer, you may wish to read pixels from a region centered around the pixel of interest, and then average. This compensates for the noise added during encoding, and also provides some interpolation between the quantized normals.
There are two colormap entries that should not be used in such a scheme: colors 0 and 255, corresponding to foreground and background (usually black and white, respectively). Users expect black text on a white background, and that should be the default. If a tool decides to change these colors that's fine, but the normal encoding process should leave them alone.
There are many ways to convert a normal into an 8-bit quantity. A number of schemes were tested, including: encode just the x and y componenets, four bits each (z is determined by z = 1-x-y); encode polar co-ordinates r and q, four bits each (r = SqRt[x2+y2], q = ArcCos[y, x]), and a single value representing arc-length around an equal-area spiral. These all required special tests to avoid the foreground and background colors. Some also had wasted entries (for example, using four bits for each of x and y means that there will be an entry for [1, 1], which is clearly unrealizable by unit-length vectors).
The encoding technique
The solution finally adopted is the use of a pair of tables, one associating normals with indices, and the other associating indices with normals. To encode a normal, one looks it up in the table, and receives an index number giving the colormap entry for that normal. To decode, one looks up the index number in the other table.
The correlation between indices and the geometry of the normals is not trivial, as it is with the cases above. Consider a 17-by-17 grid of squares, exactly enclosing a circle; the circle's center is at (8.5, 8.5) with a radius of 8.5. There are 249 squares with at least some of their area within the circle. For each of these squares, one finds the (x, y) co-ordinates at the center, and treats those as the first two co-ordinates of an associated normal.
The approach is to scan the grid from left to right, top to bottom, finding those squares within the circle and filling up the tables with normals for those squares.
For example, consider the following 5-by-5 grid, centered within the larger 17-by-17 grid:
[Artwork node; type 'Artwork on' to command tool]
The grid under consideration is (10, 7); its center is (10.5, 7.5). Its normalized (x,y) coordinates are ((10.5-8.5)/8.5, (7.5-8.5)/8.5) = (0.2352941, -0.1176471). Since z = SqRt[1.0 - (x2+y2)], the normal at this square is (0.2352941, -0.1176471, 0.9647776).
Assume that squares [0 .. s] have been filled so far, so this is the s+1'st normal to be encoded. So now GtoNTable[s+1] ← (0.2352941, -0.1176471, 0.9647776), which associates Grayvalue s+1 with this normal (hence GtoN for "Grey to Normal").
The other table, NtoGTable, associates the colormap entry s+1 with a quantized version of this normal. Clearly it's easiest to use the values in the grid for the x and y components, so NtoGTable[10][7] ← s+1.
On a 17-by-17 grid, 249 entries, [6 .. 254] ,will be filled this way . Entries 0 and 255 are reserved for foreground and background colors (usually black and white, respectively). Entries [1..5] are available for use by the program. In the lighting-and-surface tool, color 1 is for the light source color display and color 2 is for the surface color display.
— Andrew Glassner