diff --git a/PluginCommon/AddressTranslate.cs b/PluginCommon/AddressTranslate.cs index fd6af4e..d57ea72 100644 --- a/PluginCommon/AddressTranslate.cs +++ b/PluginCommon/AddressTranslate.cs @@ -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")); } diff --git a/PluginCommon/Interfaces.cs b/PluginCommon/Interfaces.cs index 857362c..8da317f 100644 --- a/PluginCommon/Interfaces.cs +++ b/PluginCommon/Interfaces.cs @@ -326,9 +326,9 @@ namespace PluginCommon { /// /// 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. /// diff --git a/PluginCommon/Matrix44.cs b/PluginCommon/Matrix33.cs similarity index 70% rename from PluginCommon/Matrix44.cs rename to PluginCommon/Matrix33.cs index 9e52184..f081bd9 100644 --- a/PluginCommon/Matrix44.cs +++ b/PluginCommon/Matrix33.cs @@ -21,20 +21,22 @@ namespace PluginCommon { /// /// Simple 4x4 matrix. /// - 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 }; + /// + /// Rotation mode. Determines the order in which axes are rotated, and whether the + /// rotation is for a right-handed or left-handed system. + /// + public enum RotMode { XYZ_RRR, ZYX_RRR, ZYX_LLL, ZXY_RRR }; /// - /// 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. /// /// Rotation about the X axis, in degrees. /// Rotation about the Y axis, in degrees. /// Rotation about the Z axis, in degrees. - 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; } /// - /// Multiplies a 3-element vector. The vector's 4th element is implicitly set to 1. + /// Multiplies a 3-element vector. /// /// Column vector to multiply. /// Result vector. 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(); } diff --git a/SourceGen/RuntimeData/Help/visualization.html b/SourceGen/RuntimeData/Help/visualization.html index cb5cd0d..4bf4c46 100644 --- a/SourceGen/RuntimeData/Help/visualization.html +++ b/SourceGen/RuntimeData/Help/visualization.html @@ -105,13 +105,17 @@ If you enter an invalid value, the parameter description will turn red.

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.

+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.

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).

diff --git a/SourceGen/SGTestData/Visualization/wireframe-test b/SourceGen/SGTestData/Visualization/wireframe-test index 8f644ca..46d5d05 100644 Binary files a/SourceGen/SGTestData/Visualization/wireframe-test and b/SourceGen/SGTestData/Visualization/wireframe-test differ diff --git a/SourceGen/SGTestData/Visualization/wireframe-test.S b/SourceGen/SGTestData/Visualization/wireframe-test.S index e568f8b..d142799 100644 --- a/SourceGen/SGTestData/Visualization/wireframe-test.S +++ b/SourceGen/SGTestData/Visualization/wireframe-test.S @@ -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 diff --git a/SourceGen/SGTestData/Visualization/wireframe-test.dis65 b/SourceGen/SGTestData/Visualization/wireframe-test.dis65 index c78de9b..c81b6a5 100644 --- a/SourceGen/SGTestData/Visualization/wireframe-test.dis65 +++ b/SourceGen/SGTestData/Visualization/wireframe-test.dis65 @@ -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", diff --git a/SourceGen/WireframeObject.cs b/SourceGen/WireframeObject.cs index 94ef108..dfa124a 100644 --- a/SourceGen/WireframeObject.cs +++ b/SourceGen/WireframeObject.cs @@ -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);