Add Atari AVG visualizer

This converts AVG commands to wireframes.  We don't try to track
color or intensity.  (This is a disassembler, not a graphics
converter; perfection is not required.)  The various rotation and
animation options are still enabled, though they're not terribly
useful for this.

Commands that are meant to be used in series, such as font glyphs,
tend to use (0,0) as their left edge and baseline.  This puts the
shape in the upper-right corner of the thumbnail, which makes
everything smaller.  The change adds a "re-center" option to the
wireframe renderer that computes the visible bounds and adjusts
the coordinates so that the center of the object is at (0,0) for
display.
This commit is contained in:
Andy McFadden 2020-04-11 17:24:21 -07:00
parent df823f4c09
commit 356492d6da
7 changed files with 401 additions and 18 deletions

View File

@ -191,6 +191,9 @@ namespace PluginCommon {
/// </summary>
public string UiName { get; private set; }
/// <summary>
/// Visualization type, used to distinguish between bitmaps and wireframes.
/// </summary>
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();

View File

@ -25,8 +25,13 @@ namespace PluginCommon {
/// </summary>
[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<float> mVerticesX = new List<float>();
private List<float> mVerticesY = new List<float>();
@ -255,6 +263,8 @@ namespace PluginCommon {
// IVisualizationWireframe implementation.
//
public bool Is2d { get; set; }
public float[] GetVerticesX() {
return mVerticesX.ToArray();
}

View File

@ -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 {
/// <summary>
/// Visualizer for Atari Analog Vector Generator commands.
/// </summary>
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<string, object> parms) {
mAppRef.ReportError("2d not supported");
return null;
}
// IPlugin_Visualizer_v2
public IVisualizationWireframe GenerateWireframe(VisDescr descr,
ReadOnlyDictionary<string, object> 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<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 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;
}
}
}

View File

@ -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;

View File

@ -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);
}
/// <summary>
@ -282,7 +284,8 @@ namespace SourceGen {
/// and GIF exports.
/// </summary>
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);
}
/// <summary>
@ -364,7 +369,8 @@ namespace SourceGen {
/// coordinates so they fit within the box.
/// </summary>
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<WireframeObject.LineSeg> 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,

View File

@ -86,11 +86,14 @@ namespace SourceGen {
}
}
private bool mIs2d = false;
private List<Vertex> mVertices = new List<Vertex>();
private List<Vertex> mPoints = new List<Vertex>();
private List<Edge> mEdges = new List<Edge>();
private List<Face> mFaces = new List<Face>();
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 {
/// <param name="eulerZ">Rotation about Z axis.</params>
/// <param name="doPersp">Perspective or othographic projection?</param>
/// <param name="doBfc">Perform backface culling?</param>
/// <param name="doRecenter">Re-center 2D renderings?</param>
/// <returns>List a of line segments, which could be empty if backface culling
/// was especially successful. All segment coordinates are in the range
/// [-1,1].</returns>
public List<LineSeg> 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<LineSeg> segs = new List<LineSeg>(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) {

View File

@ -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);
}
}
}