Switch to left-handed coordinate system

There's no "standard" coordinate system, so the choice is arbitrary.
However, an examination of the Transporter mesh in Elite revealed
that the mesh was designed for a left-handed coordinate system.  We
can compensate for that trivially in the Elite visualizer, but we
might as well match what they're doing.  (The only change required
in the code is a couple of sign changes on the Z coordinate, and an
update to the rotation matrix.)

This also downsizes Matrix44 to Matrix33, exposes the rotation mode
enum, and adds a left-handed ZYX rotation mode.

This does mean that meshes that put the front at +Z will show their
backsides initially, since we're now oriented as if we're flying
the ships rather than facing them.  I considered adding a 180-degree
Y rotation (with a tweak to the rotation matrix handedness to correct
the first rotation axis) to have them facing by default, but figured
that might be confusing since +Z is supposed to be away.

Anybody who really wants it to be the other way can trivially flip
the coordinates in their visualizer (negate xc/zc).

The Z coordinates in the visualization test project were flipped so
that the design is still facing the viewer at rotation (0,0,0).
This commit is contained in:
Andy McFadden 2020-03-14 10:25:17 -07:00
parent 8ff1fa7034
commit b3dacc2613
8 changed files with 94 additions and 59 deletions

View File

@ -82,7 +82,7 @@ namespace PluginCommon {
}
try {
byte foo = data[offset];
} catch (Exception ex) {
} catch (Exception) {
throw new AddressTranslateException("FAILED at srcOff=$" + srcOffset.ToString("x4") +
" addr=$" + address.ToString("x4"));
}

View File

@ -326,9 +326,9 @@ namespace PluginCommon {
/// <summary>
/// Holds raw vertex/edge/normal data collected from a 2D or 3D wireframe mesh. We use
/// a typical right-handed coordinate system (Z comes out of the screen), with the
/// expectation that the mesh will be presented such that the object is right-side up
/// and facing toward the viewer (nose toward +Z).
/// a left-handed coordinate system (+Z goes into the screen). If the project being
/// disassembled uses different definitions for the axes, it's probably best to convert.
/// 2D data should use X/Y with Z=0.
///
/// All objects will have vertices and edges. Face normals are optional.
/// </summary>

View File

@ -21,20 +21,22 @@ namespace PluginCommon {
/// <summary>
/// Simple 4x4 matrix.
/// </summary>
public class Matrix44 {
public class Matrix33 {
private const int DIM = 3;
public double[,] Val {
get { return mVal; }
private set { mVal = value; }
}
private double[,] mVal;
public Matrix44() {
Val = new double[4, 4];
public Matrix33() {
Val = new double[DIM, DIM];
}
public void Clear() {
for (int col = 0; col < 4; col++) {
for (int row = 0; row < 4; row++) {
for (int col = 0; col < DIM; col++) {
for (int row = 0; row < DIM; row++) {
Val[col, row] = 0.0;
}
}
@ -42,18 +44,23 @@ namespace PluginCommon {
public void SetToIdentity() {
Clear();
Val[0, 0] = Val[1, 1] = Val[2, 2] = Val[3, 3] = 1.0;
Val[0, 0] = Val[1, 1] = Val[2, 2] = 1.0;
}
private enum RotMode { XYZ, ZYX, ZXY };
/// <summary>
/// Rotation mode. Determines the order in which axes are rotated, and whether the
/// rotation is for a right-handed or left-handed system.
/// </summary>
public enum RotMode { XYZ_RRR, ZYX_RRR, ZYX_LLL, ZXY_RRR };
/// <summary>
/// Sets the matrix to perform rotation about Euler angles in the order X, Y, Z.
/// Sets the matrix to perform rotation about Euler angles X/Y/Z, with a
/// configurable order.
/// </summary>
/// <param name="xdeg">Rotation about the X axis, in degrees.</param>
/// <param name="ydeg">Rotation about the Y axis, in degrees.</param>
/// <param name="zdeg">Rotation about the Z axis, in degrees.</param>
public void SetRotationEuler(int xdeg, int ydeg, int zdeg) {
public void SetRotationEuler(int xdeg, int ydeg, int zdeg, RotMode mode) {
const double degToRad = Math.PI / 180.0;
double xrad = xdeg * degToRad;
double yrad = ydeg * degToRad;
@ -68,10 +75,9 @@ namespace PluginCommon {
double sycx = sy * cx;
double sysx = sy * sx;
RotMode rm = RotMode.ZYX;
switch (rm) {
case RotMode.ZYX:
// R = Rz * Ry * Rx (from wikipedia)
switch (mode) {
case RotMode.ZYX_RRR:
// R = Rz * Ry * Rx, right-handed
Val[0, 0] = cz * cy;
Val[0, 1] = sz * cy;
Val[0, 2] = -sy;
@ -84,8 +90,8 @@ namespace PluginCommon {
Val[2, 1] = sz * sycx - cz * sx;
Val[2, 2] = cy * cx;
break;
case RotMode.XYZ:
// R = Rx * Ry * Rz (from Arc3D)
case RotMode.ZYX_LLL:
// R = Rz * Ry * Rx, left-handed
Val[0, 0] = cz * cy;
Val[0, 1] = -sz * cy;
Val[0, 2] = sy;
@ -98,8 +104,22 @@ namespace PluginCommon {
Val[2, 1] = sz * sycx + cz * sx;
Val[2, 2] = cy * cx;
break;
case RotMode.ZXY:
// R = Rz * Rx * Ry (from Arc3D)
case RotMode.XYZ_RRR:
// R = Rx * Ry * Rz
Val[0, 0] = cz * cy;
Val[0, 1] = -sz * cy;
Val[0, 2] = sy;
Val[1, 0] = cz * sysx + sz * cx;
Val[1, 1] = -sz * sysx + cz * cx;
Val[1, 2] = -cy * sx;
Val[2, 0] = -cz * sycx + sz * sx;
Val[2, 1] = sz * sycx + cz * sx;
Val[2, 2] = cy * cx;
break;
case RotMode.ZXY_RRR:
// R = Rz * Rx * Ry
double cysx = cy * sx;
Val[0, 0] = cz * cy + sz * sysx;
Val[0, 1] = -sz * cy + cz * sysx;
@ -114,29 +134,27 @@ namespace PluginCommon {
Val[2, 2] = cy * cx;
break;
}
//Val[0, 3] = Val[1, 3] = Val[2, 3] = Val[3, 0] = Val[3, 1] = Val[3, 2] = 0.0;
Val[3, 3] = 1.0;
}
/// <summary>
/// Multiplies a 3-element vector. The vector's 4th element is implicitly set to 1.
/// Multiplies a 3-element vector.
/// </summary>
/// <param name="vec">Column vector to multiply.</param>
/// <returns>Result vector.</returns>
public Vector3 Multiply(Vector3 vec) {
double rx = vec.X * Val[0, 0] + vec.Y * Val[1, 0] + vec.Z * Val[2, 0] + Val[3, 0];
double ry = vec.X * Val[0, 1] + vec.Y * Val[1, 1] + vec.Z * Val[2, 1] + Val[3, 1];
double rz = vec.X * Val[0, 2] + vec.Y * Val[1, 2] + vec.Z * Val[2, 2] + Val[3, 2];
double rx = vec.X * Val[0, 0] + vec.Y * Val[1, 0] + vec.Z * Val[2, 0];
double ry = vec.X * Val[0, 1] + vec.Y * Val[1, 1] + vec.Z * Val[2, 1];
double rz = vec.X * Val[0, 2] + vec.Y * Val[1, 2] + vec.Z * Val[2, 2];
return new Vector3(rx, ry, rz);
}
public override string ToString() {
StringBuilder sb = new StringBuilder();
for (int row = 0; row < 4; row++) {
sb.AppendFormat("|{0,8:N3} {1,8:N3} {2,8:N3} {3,8:N3}|",
Val[0, row], Val[1, row], Val[2, row], Val[3, row]);
for (int row = 0; row < DIM; row++) {
sb.AppendLine();
sb.AppendFormat("|{0,8:N3} {1,8:N3} {2,8:N3}|",
Val[0, row], Val[1, row], Val[2, row]);
}
return sb.ToString();
}

View File

@ -105,13 +105,17 @@ If you enter an invalid value, the parameter description will turn red.</p>
<p>The wireframe generator may offer the choice of perspective vs.
orthographic projection, and whether or not to enable backface
culling. If the generator doesn't provide them, the default is to
render with a perspective projection and without culling.</p>
culling. These are declared in the visualization generator script,
but implemented in the viewer. If the generator doesn't
declare them, the default is to render with a perspective projection
and without culling.</p>
<p>The viewer allows you to rotate the image about the X, Y, and Z
axes. The viewer provides a conventional right-handed coordinate system,
axes. The viewer provides a left-handed coordinate system,
with +X toward the right, +Y toward the top of the screen, and +Z
coming out of the screen. Positive rotations cause a counter-clockwise
rotation when looking down the axis in the positive direction. The
going into the screen. The object will be placed a short distance
down the Z axis and scaled to fit the window.
Positive rotations cause a counter-clockwise rotation when the axis
about which rotations are performed points toward the viewer. The
rotations are performed with a matrix using Euler angles, and are
subject to gimbal lock (e.g. if you set Y to 90 degrees, X and Z rotate
about the same axis).</p>

View File

@ -3,6 +3,9 @@
;
; Assembler: Merlin 32
; Cube with a decoration on the front (should look like a "7").
; Defined in a left-handed coordinate system (+Z away from viewer).
org $1000
lda vertices
@ -12,19 +15,19 @@
; List of vertices (X,Y,Z).
vertices
dfb -32,32,32 ;0
dfb -32,-32,32 ;1
dfb 32,-32,32 ;2
dfb 32,32,32 ;3
dfb -32,32,-32 ;4
dfb -32,-32,-32 ;5
dfb 32,-32,-32 ;6
dfb 32,32,-32 ;7
dfb -32,32,-32 ;0
dfb -32,-32,-32 ;1
dfb 32,-32,-32 ;2
dfb 32,32,-32 ;3
dfb -32,32,32 ;4
dfb -32,-32,32 ;5
dfb 32,-32,32 ;6
dfb 32,32,32 ;7
; put a decoration on the front face; should look like a '7'
dfb -20,-20,32 ;8
dfb 20,20,32 ;9
dfb 10,20,32 ;10
; Put a decoration on the front face.
dfb -20,-20,-32 ;8
dfb 20,20,-32 ;9
dfb 10,20,-32 ;10
dfb $80
; List of edges (vertex0, vertex1, face0, face1).
@ -48,8 +51,8 @@ edges
; List of faces (surface normal X,Y,Z).
faces
dfb 0,0,1 ;0 front
dfb 0,0,-1 ;1 back
dfb 0,0,-1 ;0 front
dfb 0,0,1 ;1 back
dfb 0,1,0 ;2 top
dfb 0,-1,0 ;3 bottom
dfb 1,0,0 ;4 right

View File

@ -2,7 +2,7 @@
{
"_ContentVersion":3,
"FileDataLength":120,
"FileDataCrc32":1015994132,
"FileDataCrc32":-604155558,
"ProjectProps":{
"CpuName":"6502",
"IncludeUndocumentedInstr":false,
@ -274,7 +274,7 @@
"_deltaRotY":6,
"_deltaRotZ":0,
"_frameCount":120,
"_frameDelayMsec":100}},
"_frameDelayMsec":33}},
{
"Tag":"bmp_data",

View File

@ -246,8 +246,18 @@ namespace SourceGen {
scale = (scale * zadj) / (zadj + 0.3);
}
Matrix44 rotMat = new Matrix44();
rotMat.SetRotationEuler(eulerX, eulerY, eulerZ);
// In a left-handed coordinate system, +Z is away from the viewer. The
// visualizer expects a left-handed system with the "nose" aimed toward +Z,
// which leaves us looking at the back end of things. We can add a 180 degree
// rotation about Y so we're looking at the front instead, though this
// effectively reverses the direction of rotation about X. We can compensate
// for it by reversing the handedness of the X rotation.
//eulerY = (eulerY + 180) % 360;
// Form rotation matrix.
Matrix33 rotMat = new Matrix33();
rotMat.SetRotationEuler(eulerX, eulerY, eulerZ, Matrix33.RotMode.ZYX_LLL);
//Debug.WriteLine("ROT: " + rotMat);
if (doBfc) {
// Mark faces as visible or not. This is determined with the surface normal,
@ -264,9 +274,9 @@ namespace SourceGen {
face.IsVisible = true;
continue;
}
Vector3 camVec = rotMat.Multiply(face.Vert.Vec);
Vector3 camVec = rotMat.Multiply(face.Vert.Vec); // transform
camVec = camVec.Multiply(-scale); // scale to [-1,1] and negate to get -C
camVec = camVec.Add(new Vector3(0, 0, zadj)); // translate
camVec = camVec.Add(new Vector3(0, 0, -zadj)); // translate
// Now compute the dot product of the camera vector.
double dot = Vector3.Dot(camVec, rotNorm);
@ -278,7 +288,7 @@ namespace SourceGen {
// For orthographic projection, the camera is essentially looking
// down the Z axis at every X,Y, so we can trivially check the
// value of Z in the transformed normal.
face.IsVisible = (rotNorm.Z >= 0);
face.IsVisible = (rotNorm.Z <= 0);
}
}
}
@ -300,9 +310,9 @@ namespace SourceGen {
double x0, y0, x1, y1;
if (doPersp) {
// +Z on the shape is closer to the viewer, so we negate it here
double z0 = -trv0.Z * scale;
double z1 = -trv1.Z * scale;
// Left-handed system, so +Z is away from viewer.
double z0 = trv0.Z * scale;
double z1 = trv1.Z * scale;
x0 = (trv0.X * scale * zadj) / (zadj + z0);
y0 = (trv0.Y * scale * zadj) / (zadj + z0);
x1 = (trv1.X * scale * zadj) / (zadj + z1);