/// <summary> /// Adds a face to the edge's face list if the face index is in range. (Some /// shapes, such as "plate / alloys", use $ff for their edge face value even /// though fewer than 16 faces are defined.) /// </summary> private void AddEdgeFace(VisWireframe vw, int eindex, int face, int faceCount) { if (face < faceCount) { vw.AddEdgeFace(eindex, face); } }
/// <summary> /// Adds a face to the vertex's face list if the face index is in range. (Some /// shapes, such as the Cobra Mk III, use $ff for their vertex face values even /// though fewer than 16 faces are defined.) /// </summary> private void AddVertexFace(VisWireframe vw, int vindex, int face, int faceCount) { if (face < faceCount) { vw.AddVertexFace(vindex, face); } }
private IVisualizationWireframe GenerateWireframe(ReadOnlyDictionary <string, object> parms) { int offset = Util.GetFromObjDict(parms, P_OFFSET, 0); if (offset < 0 || offset >= mFileData.Length) { // should be caught by editor mAppRef.ReportError("Invalid parameter"); return(null); } VisWireframe vw = new VisWireframe(); const sbyte END_MARKER = -128; // 0x80 try { while (true) { int vx = (sbyte)mFileData[offset++]; if (vx == END_MARKER) { break; } int vy = (sbyte)mFileData[offset++]; int vz = (sbyte)mFileData[offset++]; vw.AddVertex(vx, vy, vz); } while (true) { int v0 = (sbyte)mFileData[offset++]; if (v0 == END_MARKER) { break; } int v1 = mFileData[offset++]; int f0 = mFileData[offset++]; int f1 = mFileData[offset++]; int edge = vw.AddEdge(v0, v1); vw.AddEdgeFace(edge, f0); if (f1 != f0) { vw.AddEdgeFace(edge, f1); } } while (true) { int nx = (sbyte)mFileData[offset++]; if (nx == END_MARKER) { break; } int ny = (sbyte)mFileData[offset++]; int nz = (sbyte)mFileData[offset++]; vw.AddFaceNormal(nx, ny, nz); } } catch (IndexOutOfRangeException) { // assume it was our file data access that caused the failure mAppRef.ReportError("Ran off end of file"); return(null); } string msg; if (!vw.Validate(out msg)) { mAppRef.ReportError("Data error: " + msg); return(null); } return(vw); }
private IVisualizationWireframe GenerateWireframe(ReadOnlyDictionary <string, object> parms) { int offset = Util.GetFromObjDict(parms, P_OFFSET, 0); int baseAddr = Util.GetFromObjDict(parms, P_BASE_ADDR, 0); if (offset < 0 || offset >= mFileData.Length) { // should be caught by editor mAppRef.ReportError("Invalid parameter"); return(null); } VisWireframe vw = new VisWireframe(); vw.Is2d = true; try { int[] stack = new int[4]; int stackPtr = 0; int beamX = 0; int beamY = 0; double scale = 1.0; bool done = false; int centerVertex = vw.AddVertex(0, 0, 0); int curVertex = centerVertex; while (!done && offset < mFileData.Length) { ushort code0 = (ushort)Util.GetWord(mFileData, offset, 2, false); offset += 2; Opcode opc = GetOpcode(code0); int dx, dy, ii, vaddr; switch (opc) { case Opcode.VCTR: // 000YYYYY YYYYYYYY IIIXXXXX XXXXXXXX ushort code1 = (ushort)Util.GetWord(mFileData, offset, 2, false); offset += 2; dy = sign13(code0 & 0x1fff); dx = sign13(code1 & 0x1fff); ii = code1 >> 13; beamX += (int)Math.Round(dx * scale); beamY += (int)Math.Round(dy * scale); if (ii == 0) { // move only curVertex = vw.AddVertex(beamX, beamY, 0); } else if (dx == 0 && dy == 0) { // plot point vw.AddPoint(curVertex); //mAppRef.DebugLog("PLOT v" + curVertex + ": " // + beamX + "," + beamY); } else { // draw line from previous vertex int newVertex = vw.AddVertex(beamX, beamY, 0); vw.AddEdge(curVertex, newVertex); curVertex = newVertex; } break; case Opcode.HALT: // 00100000 00100000 if (stackPtr != 0) { mAppRef.DebugLog("NOTE: encountered HALT with nonzero stack"); } done = true; break; case Opcode.SVEC: // 010YYYYY IIIXXXXX dy = sign5((code0 >> 8) & 0x1f) * 2; dx = sign5(code0 & 0x1f) * 2; ii = (code0 >> 5) & 0x07; if (ii != 1) { ii *= 2; } // note dx/dy==0 (i.e. draw point) is not supported for SVEC beamX += (int)Math.Round(dx * scale); beamY += (int)Math.Round(dy * scale); if (ii == 0) { // move only curVertex = vw.AddVertex(beamX, beamY, 0); } else { // draw line from previous vertex int newVertex = vw.AddVertex(beamX, beamY, 0); vw.AddEdge(curVertex, newVertex); //mAppRef.DebugLog("SVEC edge " + curVertex + " - " + newVertex); curVertex = newVertex; } break; case Opcode.STAT: // 0110-EHO IIIICCCC // Note this is different for e.g. Star Wars: 0110-RGB ZZZZZZZZ ii = (code0 >> 4) & 0x0f; break; case Opcode.SCAL: // 0111-BBB LLLLLLLL // Binary scaling adjusts vector drawing time, linear scaling does // not. (Does this affect the intensity?) int bs = (code0 >> 8) & 0x07; int ls = code0 & 0xff; scale = (16384 - (ls << 6)) >> bs; break; case Opcode.CNTR: // 10000000 01------ beamX = beamY = 0; curVertex = centerVertex; break; case Opcode.JSR: // 101-AAAA AAAAAAAA vaddr = code0 & 0x0fff; // spec says 12 bits, VECSIM uses 13 if (stackPtr == stack.Length) { mAppRef.ReportError("Stack overflow at +" + offset.ToString("x6")); return(null); } stack[stackPtr++] = offset; if (!Branch(vaddr, baseAddr, ref offset)) { return(null); } break; case Opcode.RTS: // 110----- -------- if (stackPtr == 0) { done = true; } else { offset = stack[--stackPtr]; } break; case Opcode.JMP: // 111-AAAA AAAAAAAA vaddr = code0 & 0x0fff; // spec says 12 bits, VECSIM uses 13 if (!Branch(vaddr, baseAddr, ref offset)) { return(null); } break; default: mAppRef.ReportError("Unhandled code $" + code0.ToString("x4")); return(null); } } } catch (IndexOutOfRangeException) { // assume it was our file data access that caused the failure mAppRef.ReportError("Ran off end of file"); return(null); } string msg; if (!vw.Validate(out msg)) { mAppRef.ReportError("Data error: " + msg); return(null); } return(vw); }
/// <summary> /// Attempts to fix the surface normals. Call this after the shape has been /// loaded and the data validated. /// </summary> /// <param name="vw">Wireframe data.</param> /// <param name="offset">Initial shape offset.</param> private void FixNormals(VisWireframe vw, int offset) { const int minVisThresh = 0x9; int edgeOffset = offset + (short)(mFileData[offset + 0x03] | (mFileData[offset + 0x10] << 8)); int faceOffset = offset + (short)(mFileData[offset + 0x04] | (mFileData[offset + 0x11] << 8)); float[] verticesX = vw.GetVerticesX(); float[] verticesY = vw.GetVerticesY(); float[] verticesZ = vw.GetVerticesZ(); float[] normalsX = vw.GetNormalsX(); float[] normalsY = vw.GetNormalsY(); float[] normalsZ = vw.GetNormalsZ(); IntPair[] edges = vw.GetEdges(); IntPair[] edgeFaces = vw.GetEdgeFaces(); for (int face = 0; face < normalsX.Length; face++) { // Find the first edge that references this face. We ignore anything whose // visibility threshold is too low. int edge = -1; int ef; for (ef = 0; ef < edgeFaces.Length; ef++) { // pull the flags out of the edge data to get the visibility threshold byte flags = mFileData[edgeOffset + edgeFaces[ef].Val0 * 4]; if (flags > 0x1f) { mAppRef.DebugLog("BAD FLAG " + flags.ToString("x2")); } if (flags < minVisThresh) { continue; } if (edgeFaces[ef].Val1 == face) { edge = edgeFaces[ef].Val0; break; } } if (edge < 0) { mAppRef.DebugLog("Unable to find first edge for face " + face); continue; } // Extract the two vertices. int v0 = edges[edge].Val0; int v1 = edges[edge].Val1; // Find another edge for this face that has a common vertex. edge = -1; for (++ef; ef < edgeFaces.Length; ef++) { byte flags = mFileData[edgeOffset + edgeFaces[ef].Val0 * 4]; if (flags > 0x1f) { mAppRef.DebugLog("BAD FLAG " + flags.ToString("x2")); } if (flags < minVisThresh) { continue; } if (edgeFaces[ef].Val1 == face) { int chkEdge = edgeFaces[ef].Val0; if (edges[chkEdge].Val0 == v0 || edges[chkEdge].Val0 == v1 || edges[chkEdge].Val1 == v0 || edges[chkEdge].Val1 == v1) { edge = chkEdge; break; } } } if (edge < 0) { mAppRef.DebugLog("Unable to find second edge for face " + face); continue; } // Arrange the vertices so the edges are v0-v1 and v1-v2. If the edges have // v0 in common we shuffle things around. int v2; if (edges[edge].Val0 == v0) { v2 = v1; v1 = v0; v0 = edges[edge].Val1; } else if (edges[edge].Val1 == v0) { v2 = v1; v1 = v0; v0 = edges[edge].Val0; } else if (edges[edge].Val0 == v1) { v2 = edges[edge].Val1; } else if (edges[edge].Val1 == v1) { v2 = edges[edge].Val0; } else { mAppRef.DebugLog("BUG!"); continue; } //mAppRef.DebugLog("Face " + face + ": using vertices " + v0 + "," + v1 + "," + v2); // Create vectors for the edges. Vector3 vec0 = new Vector3(verticesX[v0], verticesY[v0], verticesZ[v0]); Vector3 vec1 = new Vector3(verticesX[v1], verticesY[v1], verticesZ[v1]); Vector3 vec2 = new Vector3(verticesX[v2], verticesY[v2], verticesZ[v2]); Vector3 evec0 = Vector3.Subtract(vec0, vec1); Vector3 evec1 = Vector3.Subtract(vec1, vec2); // Compute the cross product. Vector3 cross = Vector3.Cross(evec0, evec1); Vector3 negCross = cross.Multiply(-1); //mAppRef.DebugLog(" evec0=" + evec0 + " evec1=" + evec1 + " cross=" + cross); // Check to see if we got the sign backward by adding the new vector to // one of the vertices. If it moves us farther from the center of the // object, it's facing outward, and we're good. if (Vector3.Add(vec0, cross).Magnitude() < Vector3.Add(vec0, negCross).Magnitude()) { // flip it cross = negCross; } // Replace the entry. Vector3 orig = new Vector3(normalsX[face], normalsY[face], normalsZ[face]).Normalize(); Vector3 subst = cross.Normalize(); vw.ReplaceFaceNormal(face, (float)subst.X, (float)subst.Y, (float)subst.Z); //double ang = Math.Acos(Vector3.Dot(orig, subst)); //mAppRef.DebugLog("Face " + face.ToString("D2") + ": " + subst + // " vs. orig " + orig + // " (off by " + (ang * 180.0 / Math.PI).ToString("N2") + " deg)"); } }
private IVisualizationWireframe GenerateWireframe(ReadOnlyDictionary <string, object> parms) { int offset = Util.GetFromObjDict(parms, P_OFFSET, 0); int lodDist = Util.GetFromObjDict(parms, P_LOD_DISTANCE, DEFAULT_LOD_DIST); bool doFixNormals = Util.GetFromObjDict(parms, P_DO_FIX_NORMALS, true); bool doCullVertices = Util.GetFromObjDict(parms, P_DO_CULL_VERTICES, false); if (offset < 0 || offset >= mFileData.Length) { // should be caught by editor mAppRef.ReportError("Invalid parameter"); return(null); } VisWireframe vw = new VisWireframe(); try { int edgeOffset = offset + (short)(mFileData[offset + 0x03] | (mFileData[offset + 0x10] << 8)); int faceOffset = offset + (short)(mFileData[offset + 0x04] | (mFileData[offset + 0x11] << 8)); int vertexCount = mFileData[offset + 0x08] / 6; int edgeCount = mFileData[offset + 0x09]; int faceCount = mFileData[offset + 0x0c] / 4; //mAppRef.DebugLog("MESH vc=" + vertexCount + " ec=" + edgeCount + " fc=" + faceCount + // " eoff=" + edgeOffset + " foff=" + faceOffset); int vertexOffset = offset + 0x14; for (int i = 0; i < vertexCount; i++) { int xc = mFileData[vertexOffset++]; int yc = mFileData[vertexOffset++]; int zc = mFileData[vertexOffset++]; byte flags = mFileData[vertexOffset++]; byte faces0 = mFileData[vertexOffset++]; byte faces1 = mFileData[vertexOffset++]; if ((flags & 0x80) != 0) { xc = -xc; } if ((flags & 0x40) != 0) { yc = -yc; } if ((flags & 0x20) != 0) { zc = -zc; } int visThresh = flags & 0x1f; if (TURN_TO_FRONT) { xc = -xc; zc = -zc; } int vindex = vw.AddVertex(xc, yc, zc); if (doCullVertices) { AddVertexFace(vw, vindex, faces0 & 0x0f, faceCount); AddVertexFace(vw, vindex, faces0 >> 4, faceCount); AddVertexFace(vw, vindex, faces1 & 0x0f, faceCount); AddVertexFace(vw, vindex, faces1 >> 4, faceCount); } if (visThresh < lodDist) { vw.AddVertexExclusion(vindex); } //mAppRef.DebugLog("v" + i + " " + xc + "," + yc + "," + zc + // " :: " + (faces0 & 0x0f) + "," + (faces0 >> 4) + "," + // (faces1 & 0x0f) + "," + (faces1 >> 4)); } for (int i = 0; i < edgeCount; i++) { byte flags = mFileData[edgeOffset++]; byte faces = mFileData[edgeOffset++]; byte v0 = mFileData[edgeOffset++]; byte v1 = mFileData[edgeOffset++]; int visThresh = flags & 0x1f; int eindex = vw.AddEdge(v0 / 4, v1 / 4); AddEdgeFace(vw, eindex, faces & 0x0f, faceCount); AddEdgeFace(vw, eindex, faces >> 4, faceCount); if (visThresh < lodDist) { vw.AddEdgeExclusion(eindex); } //mAppRef.DebugLog("E" + i + " " + (v0 / 4) + "," + (v1 / 4) + // " :: " + (faces & 0x0f) + "," + (faces >> 4)); } for (int i = 0; i < faceCount; i++) { byte flags = mFileData[faceOffset++]; int xc = mFileData[faceOffset++]; int yc = mFileData[faceOffset++]; int zc = mFileData[faceOffset++]; if ((flags & 0x80) != 0) { xc = -xc; } if ((flags & 0x40) != 0) { yc = -yc; } if ((flags & 0x20) != 0) { zc = -zc; } if (TURN_TO_FRONT) { xc = -xc; zc = -zc; } //int visThresh = flags & 0x1f; // We don't handle the face visibility threshold, which is only used // for the "plate / alloys" hull to disable BFC. if (new Vector3(xc, yc, zc).Magnitude() == 0) { // We have two choices: // (1) Add a placeholder (say, [0,0,1]). Causes the renderer to // get confused if there's no vertex for the face. // (2) Drop it, as it's clearly not used. Potentially problematic if // there are other faces that *are* used, because we throw the // indices off by one. // So far this only seems to be a thing for "plate / alloys" which // doesn't do BFC, so I'm taking approach #2. } else { vw.AddFaceNormal(xc, yc, zc); } //mAppRef.DebugLog("F" + i + " " + xc + "," + yc + "," + zc); } } catch (IndexOutOfRangeException) { // assume it was our file data access that caused the failure mAppRef.ReportError("Ran off end of file"); return(null); } string msg; if (!vw.Validate(out msg)) { mAppRef.ReportError("Data error: " + msg); return(null); } if (doFixNormals) { FixNormals(vw, offset); } return(vw); }
private IVisualizationWireframe GenerateWireframe(ReadOnlyDictionary <string, object> parms) { int offset = Util.GetFromObjDict(parms, P_OFFSET, 0); int baseAddr = Util.GetFromObjDict(parms, P_BASE_ADDR, 0); bool ignoreLabs = Util.GetFromObjDict(parms, P_IGNORE_LABS, false); if (offset < 0 || offset >= mFileData.Length) { // should be caught by editor mAppRef.ReportError("Invalid parameter"); return(null); } VisWireframe vw = new VisWireframe(); vw.Is2d = true; try { int[] stack = new int[4]; int stackPtr = 0; double beamX = 0; double beamY = 0; int scaleFactor = 0; // tiny bool done = false; int centerVertex = vw.AddVertex(0, 0, 0); int curVertex = centerVertex; while (!done && offset < mFileData.Length) { ushort code0 = (ushort)Util.GetWord(mFileData, offset, 2, false); offset += 2; Opcode opc = GetOpcode(code0); switch (opc) { case Opcode.VCTR: { // SSSS -mYY YYYY YYYY | BBBB -mXX XXXX XXXX ushort code1 = (ushort)Util.GetWord(mFileData, offset, 2, false); offset += 2; int yval = sign11(code0 & 0x07ff); int xval = sign11(code1 & 0x07ff); int localsc = code0 >> 12; // local scale int bb = code1 >> 12; // brightness double scale = CalcScaleMult(scaleFactor + localsc); double dx = xval * scale; double dy = yval * scale; beamX += dx; beamY += dy; if (bb == 0) { // move only curVertex = vw.AddVertex((float)beamX, (float)beamY, 0); } else if (xval == 0 && yval == 0) { // plot point vw.AddPoint(curVertex); //mAppRef.DebugLog("PLOT v" + curVertex + ": " // + beamX + "," + beamY); } else { // draw line from previous vertex int newVertex = vw.AddVertex((float)beamX, (float)beamY, 0); vw.AddEdge(curVertex, newVertex); curVertex = newVertex; } if (VERBOSE) { mAppRef.DebugLog("VCTR scale=" + localsc + " x=" + dx + " y=" + dy + " b=" + bb + " --> dx=" + dx + " dy=" + dy); } } break; case Opcode.LABS: { // 1010 00yy yyyy yyyy | SSSS 00xx xxxx xxxx ushort code1 = (ushort)Util.GetWord(mFileData, offset, 2, false); offset += 2; int yc = code0 & 0x07ff; int xc = code1 & 0x07ff; int scale = code1 >> 12; if (!ignoreLabs) { // Some things do a big screen movement before they start // drawing, which throws off the auto-scaling. The output // looks better if we ignore the initial movement. beamX = xc; beamY = yc; } // Sign-extend the scale factor. (It's usually 0 or 1 in ROM.) byte left = (byte)(scale << 4); scaleFactor = (sbyte)left >> 4; if (VERBOSE) { mAppRef.DebugLog("LABS scale=" + scale + " x=" + xc + " y=" + yc); } } break; case Opcode.HALT: // 1011 0000 0000 0000 if (stackPtr != 0) { mAppRef.DebugLog("NOTE: encountered HALT with nonzero stack"); } done = true; break; case Opcode.JSRL: { // 1100 aaaa aaaa aaaa int vaddr = code0 & 0x0fff; if (stackPtr == stack.Length) { mAppRef.ReportError("Stack overflow at +" + offset.ToString("x6")); return(null); } stack[stackPtr++] = offset; if (!Branch(vaddr, baseAddr, ref offset)) { return(null); } } break; case Opcode.JMPL: { // 1110 aaaa aaaa aaaa int vaddr = code0 & 0x0fff; if (!Branch(vaddr, baseAddr, ref offset)) { return(null); } } break; case Opcode.RTSL: // 1101 0000 0000 0000 if (stackPtr == 0) { done = true; } else { offset = stack[--stackPtr]; } break; case Opcode.SVEC: { // 1111 smYY BBBB SmXX int yval = sign3((code0 >> 8) & 0x07); int xval = sign3(code0 & 0x07); int localsc = ((code0 >> 11) & 0x01) | ((code0 >> 2) & 0x02); // SVEC scale is VEC scale + 2 double scale = CalcScaleMult(scaleFactor + localsc + 2); int bb = (code0 >> 4) & 0x0f; // The dx/dy values need to be x256 to make them work right. // This is not mentioned in any document I've found, but it's // required if e.g. you want the hexagon to match up with the 'C' // in the Asteroids copyright message. double dx = (xval << 8) * scale; double dy = (yval << 8) * scale; beamX += dx; beamY += dy; if (bb == 0) { // move only curVertex = vw.AddVertex((float)beamX, (float)beamY, 0); } else if (xval == 0 && yval == 0) { // plot point vw.AddPoint(curVertex); //mAppRef.DebugLog("SPLOT v" + curVertex + ": " // + beamX + "," + beamY); } else { // draw line from previous vertex int newVertex = vw.AddVertex((float)beamX, (float)beamY, 0); vw.AddEdge(curVertex, newVertex); curVertex = newVertex; } if (VERBOSE) { mAppRef.DebugLog("SVEC scale=" + localsc + " x=" + dx + " y=" + dy + " b=" + bb + " --> dx=" + dx + " dy=" + dy); } } break; default: mAppRef.ReportError("Unhandled code $" + code0.ToString("x4")); return(null); } } } catch (IndexOutOfRangeException) { // assume it was our file data access that caused the failure mAppRef.ReportError("Ran off end of file"); return(null); } string msg; if (!vw.Validate(out msg)) { mAppRef.ReportError("Data error: " + msg); return(null); } return(vw); }