Example #1
0
 /// <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);
     }
 }
Example #2
0
 /// <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);
     }
 }
Example #3
0
        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);
        }
Example #4
0
        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);
        }
Example #5
0
        /// <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)");
            }
        }
Example #6
0
        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);
        }
Example #7
0
        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);
        }