diff --git a/PluginCommon/Interfaces.cs b/PluginCommon/Interfaces.cs index 22528dc..612dcc7 100644 --- a/PluginCommon/Interfaces.cs +++ b/PluginCommon/Interfaces.cs @@ -191,6 +191,9 @@ namespace PluginCommon { /// public string UiName { get; private set; } + /// + /// Visualization type, used to distinguish between bitmaps and wireframes. + /// public enum VisType { Unknown = 0, Bitmap, // 2D bitmap @@ -349,6 +352,8 @@ namespace PluginCommon { public interface IVisualizationWireframe { // Each function returns the specified data. Do not modify the returned arrays. + bool Is2d { get; } + float[] GetVerticesX(); float[] GetVerticesY(); float[] GetVerticesZ(); diff --git a/PluginCommon/VisWireframe.cs b/PluginCommon/VisWireframe.cs index 83b1928..814d465 100644 --- a/PluginCommon/VisWireframe.cs +++ b/PluginCommon/VisWireframe.cs @@ -25,8 +25,13 @@ namespace PluginCommon { /// [Serializable] public class VisWireframe : IVisualizationWireframe { + // + // Names and definitions of parameters that are interpreted by the wireframe + // renderer, rather than the visualization generator. + // public const string P_IS_PERSPECTIVE = "_isPerspective"; public const string P_IS_BFC_ENABLED = "_isBfcEnabled"; + public const string P_IS_RECENTERED = "_isRecentered"; public static VisParamDescr Param_IsPerspective(string uiLabel, bool defaultVal) { return new VisParamDescr(uiLabel, P_IS_PERSPECTIVE, typeof(bool), 0, 0, 0, defaultVal); @@ -34,6 +39,9 @@ namespace PluginCommon { public static VisParamDescr Param_IsBfcEnabled(string uiLabel, bool defaultVal) { return new VisParamDescr(uiLabel, P_IS_BFC_ENABLED, typeof(bool), 0, 0, 0, defaultVal); } + public static VisParamDescr Param_IsRecentered(string uiLabel, bool defaultVal) { + return new VisParamDescr(uiLabel, P_IS_RECENTERED, typeof(bool), 0, 0, 0, defaultVal); + } private List mVerticesX = new List(); private List mVerticesY = new List(); @@ -255,6 +263,8 @@ namespace PluginCommon { // IVisualizationWireframe implementation. // + public bool Is2d { get; set; } + public float[] GetVerticesX() { return mVerticesX.ToArray(); } diff --git a/SourceGen/RuntimeData/Atari/VisAVG.cs b/SourceGen/RuntimeData/Atari/VisAVG.cs new file mode 100644 index 0000000..9318726 --- /dev/null +++ b/SourceGen/RuntimeData/Atari/VisAVG.cs @@ -0,0 +1,276 @@ +/* + * Copyright 2020 faddenSoft + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.ObjectModel; + +using PluginCommon; + +namespace RuntimeData.Atari { + /// + /// Visualizer for Atari Analog Vector Generator commands. + /// + public class VisAVG : MarshalByRefObject, IPlugin, IPlugin_Visualizer_v2 { + // IPlugin + public string Identifier { + get { return "Atari AVG Visualizer"; } + } + private IApplication mAppRef; + private byte[] mFileData; + private AddressTranslate mAddrTrans; + + // Visualization identifiers; DO NOT change or projects that use them will break. + private const string VIS_GEN_AVG = "atari-avg"; + + private const string P_OFFSET = "offset"; + private const string P_BASE_ADDR = "baseAddr"; + + // Visualization descriptors. + private VisDescr[] mDescriptors = new VisDescr[] { + new VisDescr(VIS_GEN_AVG, "Atari AVG Commands", VisDescr.VisType.Wireframe, + new VisParamDescr[] { + new VisParamDescr("File offset (hex)", + P_OFFSET, typeof(int), 0, 0x00ffffff, VisParamDescr.SpecialMode.Offset, 0), + new VisParamDescr("Base address", + P_BASE_ADDR, typeof(int), 0x0000, 0xffff, 0, 0x2000), + + VisWireframe.Param_IsRecentered("Re-center", true), + }), + }; + + + // IPlugin + public void Prepare(IApplication appRef, byte[] fileData, AddressTranslate addrTrans) { + mAppRef = appRef; + mFileData = fileData; + mAddrTrans = addrTrans; + } + + // IPlugin + public void Unprepare() { + mAppRef = null; + mFileData = null; + mAddrTrans = null; + } + + // IPlugin_Visualizer + public VisDescr[] GetVisGenDescrs() { + if (mFileData == null) { + return null; + } + return mDescriptors; + } + + // IPlugin_Visualizer + public IVisualization2d Generate2d(VisDescr descr, + ReadOnlyDictionary parms) { + mAppRef.ReportError("2d not supported"); + return null; + } + + // IPlugin_Visualizer_v2 + public IVisualizationWireframe GenerateWireframe(VisDescr descr, + ReadOnlyDictionary parms) { + switch (descr.Ident) { + case VIS_GEN_AVG: + return GenerateWireframe(parms); + default: + mAppRef.ReportError("Unknown ident " + descr.Ident); + return null; + } + } + + private enum Opcode { + Unknown = 0, VCTR, HALT, SVEC, STAT, SCAL, CNTR, JSR, RTS, JMP + } + + private IVisualizationWireframe GenerateWireframe(ReadOnlyDictionary 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 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 + ii = (code0 >> 4) & 0x0f; + break; + case Opcode.SCAL: // 0111-BBB LLLLLLLL + 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; + 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; + 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; + } + + private Opcode GetOpcode(ushort code) { + switch (code & 0xe000) { + case 0x0000: return Opcode.VCTR; + case 0x2000: return Opcode.HALT; + case 0x4000: return Opcode.SVEC; + case 0x6000: return ((code & 0xf000) == 0x6000) ? Opcode.STAT : Opcode.SCAL; + case 0x8000: return Opcode.CNTR; + case 0xa000: return Opcode.JSR; + case 0xc000: return Opcode.RTS; + case 0xe000: return Opcode.JMP; + default: return Opcode.Unknown; // shouldn't be possible + } + } + + // Sign-extend a signed 5-bit value. + int sign5(int val) { + byte val5 = (byte)(val << 3); + return (sbyte)val5 >> 3; + } + + // Sign-extend a signed 13-bit value. + int sign13(int val) { + ushort val13 = (ushort)(val << 3); + return (short)val13 >> 3; + } + + private bool Branch(int vaddr, int baseAddr, ref int offset) { + int fileAddr = baseAddr + vaddr * 2; + int fileOffset = mAddrTrans.AddressToOffset(offset, fileAddr); + if (fileOffset < 0) { + mAppRef.ReportError("JMP/JSR to " + vaddr.ToString("x4") + " invalid"); + return false; + } + offset = fileOffset; + return true; + } + } +} diff --git a/SourceGen/VisWireframeAnimation.cs b/SourceGen/VisWireframeAnimation.cs index 8b58bdf..9c05334 100644 --- a/SourceGen/VisWireframeAnimation.cs +++ b/SourceGen/VisWireframeAnimation.cs @@ -103,6 +103,7 @@ namespace SourceGen { int frameDelayMsec = Util.GetFromObjDict(VisGenParams, P_FRAME_DELAY_MSEC, 100); bool doPersp = Util.GetFromObjDict(VisGenParams, VisWireframe.P_IS_PERSPECTIVE, true); bool doBfc = Util.GetFromObjDict(VisGenParams, VisWireframe.P_IS_BFC_ENABLED, false); + bool doRecenter = Util.GetFromObjDict(VisGenParams, VisWireframe.P_IS_RECENTERED, false); // Try to avoid System.Runtime.InteropServices.COMException (0x88980003): // MILERR_WIN32ERROR (Exception from HRESULT: 0x88980003) @@ -120,7 +121,7 @@ namespace SourceGen { for (int frame = 0; frame < frameCount; frame++) { BitmapSource bs = GenerateWireframeImage(mWireObj, dim, - curX, curY, curZ, doPersp, doBfc); + curX, curY, curZ, doPersp, doBfc, doRecenter); encoder.AddFrame(BitmapFrame.Create(bs), frameDelayMsec); curX = (curX + 360 + deltaX) % 360; diff --git a/SourceGen/Visualization.cs b/SourceGen/Visualization.cs index 14acb16..1624005 100644 --- a/SourceGen/Visualization.cs +++ b/SourceGen/Visualization.cs @@ -274,7 +274,9 @@ namespace SourceGen { int eulerZ = Util.GetFromObjDict(parms, VisWireframeAnimation.P_EULER_ROT_Z, 0); bool doPersp = Util.GetFromObjDict(parms, VisWireframe.P_IS_PERSPECTIVE, true); bool doBfc = Util.GetFromObjDict(parms, VisWireframe.P_IS_BFC_ENABLED, false); - return GenerateWireframeImage(wireObj, dim, eulerX, eulerY, eulerZ, doPersp, doBfc); + bool doRecenter = Util.GetFromObjDict(parms, VisWireframe.P_IS_RECENTERED, true); + return GenerateWireframeImage(wireObj, dim, eulerX, eulerY, eulerZ, doPersp, doBfc, + doRecenter); } /// @@ -282,7 +284,8 @@ namespace SourceGen { /// and GIF exports. /// public static BitmapSource GenerateWireframeImage(WireframeObject wireObj, - double dim, int eulerX, int eulerY, int eulerZ, bool doPersp, bool doBfc) { + double dim, int eulerX, int eulerY, int eulerZ, bool doPersp, bool doBfc, + bool doRecenter) { if (wireObj == null) { // Can happen if the visualization generator is failing on stuff loaded from // the project file. @@ -291,7 +294,7 @@ namespace SourceGen { // Generate the path geometry. GeometryGroup geo = GenerateWireframePath(wireObj, dim, eulerX, eulerY, eulerZ, - doPersp, doBfc); + doPersp, doBfc, doRecenter); // Render geometry to bitmap -- https://stackoverflow.com/a/869767/294248 Rect bounds = geo.GetRenderBounds(null); @@ -355,7 +358,9 @@ namespace SourceGen { int eulerZ = Util.GetFromObjDict(parms, VisWireframeAnimation.P_EULER_ROT_Z, 0); bool doPersp = Util.GetFromObjDict(parms, VisWireframe.P_IS_PERSPECTIVE, true); bool doBfc = Util.GetFromObjDict(parms, VisWireframe.P_IS_BFC_ENABLED, false); - return GenerateWireframePath(wireObj, dim, eulerX, eulerY, eulerZ, doPersp, doBfc); + bool doRecenter = Util.GetFromObjDict(parms, VisWireframe.P_IS_RECENTERED, true); + return GenerateWireframePath(wireObj, dim, eulerX, eulerY, eulerZ, doPersp, doBfc, + doRecenter); } /// @@ -364,7 +369,8 @@ namespace SourceGen { /// coordinates so they fit within the box. /// public static GeometryGroup GenerateWireframePath(WireframeObject wireObj, - double dim, int eulerX, int eulerY, int eulerZ, bool doPersp, bool doBfc) { + double dim, int eulerX, int eulerY, int eulerZ, bool doPersp, bool doBfc, + bool doRecenter) { // WPF path drawing is based on a system where a pixel is drawn at the center // of its coordinates, and integer coordinates start at the top left edge of // the drawing area. If you draw a pixel at (0,0), 3/4ths of the pixel will be @@ -409,7 +415,7 @@ namespace SourceGen { // Generate a list of clip-space line segments. Coordinate values are in the // range [-1,1], with +X to the right and +Y upward. List segs = wireObj.Generate(eulerX, eulerY, eulerZ, - doPersp, doBfc); + doPersp, doBfc, doRecenter); // Convert clip-space coords to screen. We need to translate to [0,2] with +Y // toward the bottom of the screen, scale up, round to the nearest whole pixel, diff --git a/SourceGen/WireframeObject.cs b/SourceGen/WireframeObject.cs index 114cc21..8739bcb 100644 --- a/SourceGen/WireframeObject.cs +++ b/SourceGen/WireframeObject.cs @@ -86,11 +86,14 @@ namespace SourceGen { } } + private bool mIs2d = false; private List mVertices = new List(); private List mPoints = new List(); private List mEdges = new List(); private List mFaces = new List(); private double mBigMag = -1.0; + private double mBigMagRc = -1.0; + private double mCenterAdjX, mCenterAdjY; // private constructor; use Create() @@ -104,11 +107,16 @@ namespace SourceGen { public static WireframeObject Create(IVisualizationWireframe visWire) { WireframeObject wireObj = new WireframeObject(); + wireObj.mIs2d = visWire.Is2d; + // // Start by extracting data from the visualization object. Everything stored // there is loaded into this object. The VisWireframe validator will have // ensured that all the indices are in range. // + // IMPORTANT: do not retain "visWire", as it may be a proxy for an object with a + // limited lifespan. + // float[] normalsX = visWire.GetNormalsX(); if (normalsX.Length > 0) { @@ -138,6 +146,13 @@ namespace SourceGen { return null; } + // Compute min/max for X/Y for 2d re-centering. The trick is that we only want + // to use vertices that are visible. If the shape starts with a huge move off to + // the left, we don't want to include (0,0). + double xmin, xmax, ymin, ymax; + xmin = ymin = 10e9; + xmax = ymax = -10e9; + for (int i = 0; i < verticesX.Length; i++) { wireObj.mVertices.Add(new Vertex(verticesX[i], verticesY[i], verticesZ[i], HasIndex(excludedVertices, i))); @@ -145,7 +160,9 @@ namespace SourceGen { int[] points = visWire.GetPoints(); for (int i = 0; i < points.Length; i++) { - wireObj.mPoints.Add(wireObj.mVertices[i]); + Vertex vert = wireObj.mVertices[points[i]]; + wireObj.mPoints.Add(vert); + UpdateMinMax(vert, ref xmin, ref xmax, ref ymin, ref ymax); } IntPair[] edges = visWire.GetEdges(); @@ -160,8 +177,12 @@ namespace SourceGen { return null; } - wireObj.mEdges.Add(new Edge(wireObj.mVertices[v0index], - wireObj.mVertices[v1index], HasIndex(excludedEdges, i))); + Vertex vert0 = wireObj.mVertices[v0index]; + Vertex vert1 = wireObj.mVertices[v1index]; + wireObj.mEdges.Add(new Edge(vert0, vert1, HasIndex(excludedEdges, i))); + + UpdateMinMax(vert0, ref xmin, ref xmax, ref ymin, ref ymax); + UpdateMinMax(vert1, ref xmin, ref xmax, ref ymin, ref ymax); } IntPair[] vfaces = visWire.GetVertexFaces(); @@ -204,19 +225,48 @@ namespace SourceGen { // All data has been loaded into friendly classes. // + // Compute center of visible vertices. + wireObj.mCenterAdjX = -(xmin + xmax) / 2; + wireObj.mCenterAdjY = -(ymin + ymax / 2); + // Compute the magnitude of the largest vertex, for scaling. double bigMag = -1.0; + double bigMagRc = -1.0; for (int i = 0; i < wireObj.mVertices.Count; i++) { - double mag = wireObj.mVertices[i].Vec.Magnitude(); + Vector3 vec = wireObj.mVertices[i].Vec; + double mag = vec.Magnitude(); if (bigMag < mag) { bigMag = mag; } + + // Repeat the operation with recentering. This isn't quite right as we're + // including all vertices, not just the visible ones. + mag = new Vector3(vec.X + wireObj.mCenterAdjX, + vec.Y + wireObj.mCenterAdjY, vec.Z).Magnitude(); + if (bigMagRc < mag) { + bigMagRc = mag; + } } wireObj.mBigMag = bigMag; + wireObj.mBigMagRc = bigMagRc; return wireObj; } + private static void UpdateMinMax(Vertex vert, ref double xmin, ref double xmax, + ref double ymin, ref double ymax) { + if (vert.Vec.X < xmin) { + xmin = vert.Vec.X; + } else if (vert.Vec.X > xmax) { + xmax = vert.Vec.X; + } + if (vert.Vec.Y < ymin) { + ymin = vert.Vec.Y; + } else if (vert.Vec.Y > ymax) { + ymax = vert.Vec.Y; + } + } + private static bool HasIndex(int[] arr, int val) { for (int i = 0; i < arr.Length; i++) { if (arr[i] == val) { @@ -235,11 +285,19 @@ namespace SourceGen { /// Rotation about Z axis. /// Perspective or othographic projection? /// Perform backface culling? + /// Re-center 2D renderings? /// List a of line segments, which could be empty if backface culling /// was especially successful. All segment coordinates are in the range /// [-1,1]. public List Generate(int eulerX, int eulerY, int eulerZ, - bool doPersp, bool doBfc) { + bool doPersp, bool doBfc, bool doRecenter) { + // overrule flags that don't make sense + if (mIs2d) { + doPersp = doBfc = false; + } else { + doRecenter = false; + } + List segs = new List(mEdges.Count); // Camera Z coordinate adjustment, used to control how perspective projections @@ -248,12 +306,25 @@ namespace SourceGen { const double zadj = 3.0; // Scale coordinate values to [-1,1]. - double scale = 1.0 / mBigMag; + double scale; + if (doRecenter) { + scale = 1.0 / mBigMagRc; + } else { + scale = 1.0 / mBigMag; + } if (doPersp) { // objects closer to camera are bigger; reduce scale slightly scale = (scale * zadj) / (zadj + 0.3); } + // Configure X/Y translation for 2D wireframes. + double transX = 0; + double transY = 0; + if (doRecenter) { + transX = mCenterAdjX; + transY = mCenterAdjY; + } + // 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 @@ -303,7 +374,12 @@ namespace SourceGen { foreach (Vertex point in mPoints) { // There are no "point faces" at the moment, so no BFC is applied. - Vector3 trv = rotMat.Multiply(point.Vec); + Vector3 vec = point.Vec; + if (doRecenter) { + vec = new Vector3(vec.X + transX, vec.Y + transY, vec.Z); + } + Vector3 trv = rotMat.Multiply(vec); + double xc, yc; if (doPersp) { double zc = trv.Z * scale; @@ -314,6 +390,8 @@ namespace SourceGen { yc = trv.Y * scale; } + //Debug.WriteLine("POINT " + xc + "," + yc); + // Zero-length line segments don't do anything. Try a '+'. const double dist = 1 / 64.0; double x0 = Math.Max(-1.0, xc - dist); @@ -336,8 +414,14 @@ namespace SourceGen { } } - Vector3 trv0 = rotMat.Multiply(edge.Vertex0.Vec); - Vector3 trv1 = rotMat.Multiply(edge.Vertex1.Vec); + Vector3 vec0 = edge.Vertex0.Vec; + Vector3 vec1 = edge.Vertex1.Vec; + if (doRecenter) { + vec0 = new Vector3(vec0.X + transX, vec0.Y + transY, vec0.Z); + vec1 = new Vector3(vec1.X + transX, vec1.Y + transY, vec1.Z); + } + Vector3 trv0 = rotMat.Multiply(vec0); + Vector3 trv1 = rotMat.Multiply(vec1); double x0, y0, x1, y1; if (doPersp) { diff --git a/SourceGen/WpfGui/ShowWireframeAnimation.xaml.cs b/SourceGen/WpfGui/ShowWireframeAnimation.xaml.cs index eaf707f..cd8e5a4 100644 --- a/SourceGen/WpfGui/ShowWireframeAnimation.xaml.cs +++ b/SourceGen/WpfGui/ShowWireframeAnimation.xaml.cs @@ -37,7 +37,7 @@ namespace SourceGen.WpfGui { private int mFrameCount; private int mInitialX, mInitialY, mInitialZ; private int mDeltaX, mDeltaY, mDeltaZ; - private bool mDoPersp, mDoBfc; + private bool mDoPersp, mDoBfc, mDoRecenter; private int mCurX, mCurY, mCurZ; @@ -59,6 +59,7 @@ namespace SourceGen.WpfGui { mFrameCount = Util.GetFromObjDict(parms, VisWireframeAnimation.P_FRAME_COUNT, 1); mDoPersp = Util.GetFromObjDict(parms, VisWireframe.P_IS_PERSPECTIVE, true); mDoBfc = Util.GetFromObjDict(parms, VisWireframe.P_IS_BFC_ENABLED, false); + mDoRecenter = Util.GetFromObjDict(parms, VisWireframe.P_IS_RECENTERED, false); int intervalMsec = Util.GetFromObjDict(parms, VisWireframeAnimation.P_FRAME_DELAY_MSEC, 100); @@ -99,7 +100,7 @@ namespace SourceGen.WpfGui { // ViewBox itself, because on the first iteration the ViewBox has a size of zero. double dim = Math.Floor(Math.Min(testBorder.ActualWidth, testBorder.ActualHeight)); wireframePath.Data = Visualization.GenerateWireframePath(mWireObj, dim, - mCurX, mCurY, mCurZ, mDoPersp, mDoBfc); + mCurX, mCurY, mCurZ, mDoPersp, mDoBfc, mDoRecenter); } } }