diff --git a/CommonUtil/Vector3.cs b/CommonUtil/Vector3.cs new file mode 100644 index 0000000..92364ce --- /dev/null +++ b/CommonUtil/Vector3.cs @@ -0,0 +1,55 @@ +/* + * 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; + +namespace CommonUtil { + /// + /// Simple 3-element column vector. + /// + public class Vector3 { + public double X { + get { return mX; } + set { mX = value; } + } + public double Y { + get { return mY; } + set { mY = value; } + } + public double Z { + get { return mZ; } + set { mZ = value; } + } + + private double mX, mY, mZ; + + public Vector3(double x, double y, double z) { + mX = x; + mY = y; + mZ = z; + } + + public double Magnitude() { + return Math.Sqrt(X * X + Y * Y + Z * Z); + } + + public void Normalize() { + double len_r = 1.0 / Magnitude(); + mX *= len_r; + mY *= len_r; + mZ *= len_r; + } + } +} diff --git a/PluginCommon/Interfaces.cs b/PluginCommon/Interfaces.cs index 9f8b6ac..b8e9c80 100644 --- a/PluginCommon/Interfaces.cs +++ b/PluginCommon/Interfaces.cs @@ -342,7 +342,7 @@ namespace PluginCommon { /// TODO(maybe): specify colors for edges. Not widely used? /// public interface IVisualizationWireframe { - // Each function returns the specified data. Do not modify. + // Each function returns the specified data. Do not modify the returned arrays. float[] GetVerticesX(); float[] GetVerticesY(); diff --git a/PluginCommon/VisWireframe.cs b/PluginCommon/VisWireframe.cs index d0fde4c..6609f10 100644 --- a/PluginCommon/VisWireframe.cs +++ b/PluginCommon/VisWireframe.cs @@ -25,20 +25,26 @@ namespace PluginCommon { /// [Serializable] public class VisWireframe : IVisualizationWireframe { + public const string P_IS_PERSPECTIVE = "_isPerspective"; + public const string P_IS_BACKFACE_REMOVED = "_isBackfaceRemoved"; + public const string P_EULER_ROT_X = "_eulerRotX"; + public const string P_EULER_ROT_Y = "_eulerRotY"; + public const string P_EULER_ROT_Z = "_eulerRotZ"; + public static VisParamDescr Param_IsPerspective(string uiLabel, bool defaultVal) { - return new VisParamDescr(uiLabel, "_isPerspective", typeof(bool), 0, 0, 0, defaultVal); + return new VisParamDescr(uiLabel, P_IS_PERSPECTIVE, typeof(bool), 0, 0, 0, defaultVal); } public static VisParamDescr Param_IsBackfaceRemoved(string uiLabel, bool defaultVal) { - return new VisParamDescr(uiLabel, "_isBackfaceRemoved", typeof(bool), 0, 0, 0, defaultVal); + return new VisParamDescr(uiLabel, P_IS_BACKFACE_REMOVED, typeof(bool), 0, 0, 0, defaultVal); } public static VisParamDescr Param_EulerX(string uiLabel, int defaultVal) { - return new VisParamDescr(uiLabel, "_eulerRotX", typeof(int), 0, 359, 0, defaultVal); + return new VisParamDescr(uiLabel, P_EULER_ROT_X, typeof(int), 0, 359, 0, defaultVal); } public static VisParamDescr Param_EulerY(string uiLabel, int defaultVal) { - return new VisParamDescr(uiLabel, "_eulerRotY", typeof(int), 0, 359, 0, defaultVal); + return new VisParamDescr(uiLabel, P_EULER_ROT_Y, typeof(int), 0, 359, 0, defaultVal); } public static VisParamDescr Param_EulerZ(string uiLabel, int defaultVal) { - return new VisParamDescr(uiLabel, "_eulerRotZ", typeof(int), 0, 359, 0, defaultVal); + return new VisParamDescr(uiLabel, P_EULER_ROT_Z, typeof(int), 0, 359, 0, defaultVal); } private List mVerticesX = new List(); @@ -136,6 +142,12 @@ namespace PluginCommon { int faceCount = mNormalsX.Count; int edgeCount = mEdges.Count; + // complain about empty objects (should we fail if no edges were defined?) + if (vertexCount == 0) { + msg = "no vertices defined"; + return false; + } + // check edges foreach (IntPair ip in mEdges) { if (ip.Val0 < 0 || ip.Val0 >= vertexCount || diff --git a/SourceGen/SGTestData/Visualization/VisWireframeTest.cs b/SourceGen/SGTestData/Visualization/VisWireframeTest.cs index 6baba6d..37c670f 100644 --- a/SourceGen/SGTestData/Visualization/VisWireframeTest.cs +++ b/SourceGen/SGTestData/Visualization/VisWireframeTest.cs @@ -124,7 +124,9 @@ namespace WireframeTest { int edge = vw.AddEdge(v0, v1); vw.AddEdgeFace(edge, f0); - vw.AddEdgeFace(edge, f1); + if (f1 != f0) { + vw.AddEdgeFace(edge, f1); + } } while (true) { diff --git a/SourceGen/SourceGen.csproj b/SourceGen/SourceGen.csproj index 10cd9fd..a718bf8 100644 --- a/SourceGen/SourceGen.csproj +++ b/SourceGen/SourceGen.csproj @@ -105,6 +105,7 @@ + AboutBox.xaml diff --git a/SourceGen/Visualization.cs b/SourceGen/Visualization.cs index 1935c15..baab770 100644 --- a/SourceGen/Visualization.cs +++ b/SourceGen/Visualization.cs @@ -172,13 +172,16 @@ namespace SourceGen { } } + /// + /// Updates the cached thumbnail image. + /// + /// Visualization object. + /// Visualization parameters. public void SetThumbnail(IVisualizationWireframe visWire, ReadOnlyDictionary parms) { - if (visWire == null) { - CachedImage = BROKEN_IMAGE; - } else { - CachedImage = GenerateWireframeImage(visWire, parms, 64); - } + Debug.Assert(visWire != null); + Debug.Assert(parms != null); + CachedImage = GenerateWireframeImage(visWire, parms, 64); } /// @@ -250,8 +253,8 @@ namespace SourceGen { // Create bitmap. RenderTargetBitmap bitmap = new RenderTargetBitmap( - (int)bounds.Width, - (int)bounds.Height, + (int)dim, + (int)dim, 96, 96, PixelFormats.Pbgra32); @@ -297,7 +300,8 @@ namespace SourceGen { // but because the thickness doesn't extend past the endpoints, the filled // area is only three. If you have a window of size 10x10, and you draw from // 0,0 to 9,9, the line will extend for half a line-thickness off the top, - // but will not go past the right/left edges. + // but will not go past the right/left edges. (This becomes very obvious when + // you're working with an up-scaled 8x8 path.) // // Similarly, drawing a horizontal line two units long results in a square, and // drawing a line that starts and ends at the same point doesn't appear to @@ -309,46 +313,39 @@ namespace SourceGen { // (9.5,1.5) is drawn as a single-wide full-brightness line. This is because of // the anti-aliasing. // - // The path has a bounding box that starts at (0,0) in the top left, and extends - // out as far as needed. If we want a path-drawn shape to animate smoothly we - // want to ensure that the bounds are constant across all renderings of a shape - // (which could get thinner or wider as it rotates), so we draw an invisible - // pixel in our desired bottom-right corner. + // The path has an axis-aligned bounding box that covers the pixel centers. If we + // want a path-drawn shape to animate smoothly we want to ensure that the bounds + // are constant across all renderings of a shape (which could get thinner or wider + // as it rotates), so we draw an invisible pixel in our desired bottom-right corner. // // If we want an 8x8 bitmap, we draw a line from (8,8) to (8,8) to establish the // bounds, then draw lines with coordinates from 0.5 to 7.5. GeometryGroup geo = new GeometryGroup(); - // This establishes the geometry bounds. It's a zero-length line segment, so - // nothing is actually drawn. Debug.WriteLine("using max=" + dim); - // TODO: currently ignoring dim - Point corner = new Point(8, 8); - geo.Children.Add(new LineGeometry(corner, corner)); - corner = new Point(0, 0); - geo.Children.Add(new LineGeometry(corner, corner)); - // TODO(xyzzy): render - //geo.Children.Add(new LineGeometry(new Point(0.0, 0.0), new Point(1.0, 0.0))); - //geo.Children.Add(new LineGeometry(new Point(0.5, 0.5), new Point(1.5, 0.5))); - //geo.Children.Add(new LineGeometry(new Point(0.75, 0.75), new Point(1.75, 0.75))); - geo.Children.Add(new LineGeometry(new Point(0.0, 0.0), new Point(5.0, 7.0))); + // Draw invisible line segments to establish Path bounds. + Point topLeft = new Point(0, 0); + Point botRight = new Point(dim, dim); + geo.Children.Add(new LineGeometry(topLeft, topLeft)); + geo.Children.Add(new LineGeometry(botRight, botRight)); - geo.Children.Add(new LineGeometry(new Point(0.5, 2), new Point(0.5, 3))); - geo.Children.Add(new LineGeometry(new Point(1.5, 3), new Point(1.5, 4))); - geo.Children.Add(new LineGeometry(new Point(2.5, 2), new Point(2.5, 3))); - geo.Children.Add(new LineGeometry(new Point(3.5, 3), new Point(3.5, 4))); - geo.Children.Add(new LineGeometry(new Point(4.5, 2), new Point(4.5, 3))); - geo.Children.Add(new LineGeometry(new Point(5.5, 3), new Point(5.5, 4))); - geo.Children.Add(new LineGeometry(new Point(6.5, 2), new Point(6.5, 3))); - geo.Children.Add(new LineGeometry(new Point(7.5, 3), new Point(7.5, 4))); + // Generate a list of clip-space line segments. Coordinate values are [-1,1]. + WireframeObject wireObj = WireframeObject.Create(visWire); + List segs = wireObj.Generate(parms); - //geo.Children.Add(new LineGeometry(new Point(4, 5), new Point(3, 5))); - //geo.Children.Add(new LineGeometry(new Point(2, 5), new Point(1, 5))); + // Convert clip-space coords to screen. We need to scale up, round them to the + // nearest whole pixel, and add +0.5 to make the thumbnails look crisp. + double scale = (dim - 0.5) / 2; + double adj = 0.5; + foreach (WireframeObject.LineSeg seg in segs) { + Point start = new Point(Math.Round((seg.X0 + 1) * scale) + adj, + Math.Round((1 - seg.Y0) * scale) + adj); + Point end = new Point(Math.Round((seg.X1 + 1) * scale) + adj, + Math.Round((1 - seg.Y1) * scale) + adj); + geo.Children.Add(new LineGeometry(start, end)); + } - //geo.Children.Add(new LineGeometry(new Point(4, 7), new Point(1, 7))); - //geo.Children.Add(new LineGeometry(new Point(5, 7), new Point(9, 7))); - //geo.Children.Add(new LineGeometry(new Point(0, 8.5), new Point(9, 8.5))); return geo; } diff --git a/SourceGen/WireframeObject.cs b/SourceGen/WireframeObject.cs new file mode 100644 index 0000000..bacf49a --- /dev/null +++ b/SourceGen/WireframeObject.cs @@ -0,0 +1,235 @@ +/* + * 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.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; + +using CommonUtil; +using PluginCommon; + +namespace SourceGen { + /// + /// Renders a wireframe visualization, generating a collection of line segments in clip space. + /// + public class WireframeObject { + /// + /// Line segment. + /// + public class LineSeg { + public double X0 { get; private set; } + public double Y0 { get; private set; } + public double X1 { get; private set; } + public double Y1 { get; private set; } + + public LineSeg(double x0, double y0, double x1, double y1) { + X0 = x0; + Y0 = y0; + X1 = x1; + Y1 = y1; + } + } + + private class Vertex { + public List Faces { get; private set; } + + public Vector3 Vec { get; private set; } + + public Vertex(double x, double y, double z) { + Vec = new Vector3(x, y, z); + Faces = new List(); + } + } + + private class Edge { + public Vertex Vertex0 { get; private set; } + public Vertex Vertex1 { get; private set; } + public List Faces { get; private set; } + + public Edge(Vertex v0, Vertex v1) { + Vertex0 = v0; + Vertex1 = v1; + Faces = new List(); + } + } + + private class Face { + public Vector3 Normal { get; private set; } + + public Face(double x, double y, double z) { + Normal = new Vector3(x, y, z); + Normal.Normalize(); + } + } + + private List mVertices = new List(); + private List mEdges = new List(); + private List mFaces = new List(); + private double mBigMag = -1.0; + + + // private constructor; use Create() + private WireframeObject() { } + + /// + /// Creates a new object from a wireframe visualization. + /// + /// Visualization object. + /// New object. + public static WireframeObject Create(IVisualizationWireframe visWire) { + WireframeObject wireObj = new WireframeObject(); + + // + // Start by extracting data from the visualization object. Everything stored + // there is loaded into this object. + // + + float[] normalsX = visWire.GetNormalsX(); + if (normalsX.Length > 0) { + float[] normalsY = visWire.GetNormalsY(); + float[] normalsZ = visWire.GetNormalsZ(); + + if (normalsX.Length != normalsY.Length || normalsX.Length != normalsZ.Length) { + Debug.Assert(false); + return null; + } + + for (int i = 0; i < normalsX.Length; i++) { + wireObj.mFaces.Add(new Face(normalsX[i], normalsY[i], normalsZ[i])); + } + } + + float[] verticesX = visWire.GetVerticesX(); + float[] verticesY = visWire.GetVerticesY(); + float[] verticesZ = visWire.GetVerticesZ(); + if (verticesX.Length == 0) { + Debug.Assert(false); + return null; + } + if (verticesX.Length != verticesY.Length || verticesX.Length != verticesZ.Length) { + Debug.Assert(false); + return null; + } + + for (int i = 0; i < verticesX.Length; i++) { + wireObj.mVertices.Add(new Vertex(verticesX[i], verticesY[i], verticesZ[i])); + } + + IntPair[] edges = visWire.GetEdges(); + for (int i = 0; i < edges.Length; i++) { + int v0index = edges[i].Val0; + int v1index = edges[i].Val1; + + if (v0index < 0 || v0index >= wireObj.mVertices.Count || + v1index < 0 || v1index >= wireObj.mVertices.Count) { + Debug.Assert(false); + return null; + } + + wireObj.mEdges.Add( + new Edge(wireObj.mVertices[v0index], wireObj.mVertices[v1index])); + } + + IntPair[] vfaces = visWire.GetVertexFaces(); + for (int i = 0; i < vfaces.Length; i++) { + int vindex = vfaces[i].Val0; + int findex = vfaces[i].Val1; + + if (vindex < 0 || vindex >= wireObj.mVertices.Count || + findex < 0 || findex >= wireObj.mFaces.Count) { + Debug.Assert(false); + return null; + } + + wireObj.mVertices[vindex].Faces.Add(wireObj.mFaces[findex]); + } + + IntPair[] efaces = visWire.GetEdgeFaces(); + for (int i = 0; i < efaces.Length; i++) { + int eindex = efaces[i].Val0; + int findex = efaces[i].Val1; + + if (eindex < 0 || eindex >= wireObj.mEdges.Count || + findex < 0 || findex >= wireObj.mFaces.Count) { + Debug.Assert(false); + return null; + } + + wireObj.mEdges[eindex].Faces.Add(wireObj.mFaces[findex]); + } + + // + // All data has been loaded into friendly classes. + // + + // Compute the magnitude of the largest vertex, for scaling. + double bigMag = -1.0; + for (int i = 0; i < wireObj.mVertices.Count; i++) { + double mag = wireObj.mVertices[i].Vec.Magnitude(); + if (bigMag < mag) { + bigMag = mag; + } + } + wireObj.mBigMag = bigMag; + + return wireObj; + } + + /// + /// Generates a list of line segments for the wireframe data and the specified + /// parameters. + /// + /// Visualization parameters. + /// List of line segments, which could be empty if backface removal + /// was especially successful. + public List Generate(ReadOnlyDictionary parms) { + List segs = new List(mEdges.Count); + + // Perspective distance adjustment. + const double zadj = 3.0; + + // Scale values to [-1,1]. + bool doPersp = Util.GetFromObjDict(parms, VisWireframe.P_IS_PERSPECTIVE, false); + double scale = 1.0 / mBigMag; + if (doPersp) { + scale = (scale * zadj) / (zadj + 1); + } + + foreach (Edge edge in mEdges) { + double x0, y0, x1, y1; + + if (doPersp) { + // +Z is closer to the viewer, so we negate it here + double z0 = -edge.Vertex0.Vec.Z * scale; + double z1 = -edge.Vertex1.Vec.Z * scale; + x0 = (edge.Vertex0.Vec.X * scale * zadj) / (zadj + z0); + y0 = (edge.Vertex0.Vec.Y * scale * zadj) / (zadj + z0); + x1 = (edge.Vertex1.Vec.X * scale * zadj) / (zadj + z1); + y1 = (edge.Vertex1.Vec.Y * scale * zadj) / (zadj + z1); + } else { + x0 = edge.Vertex0.Vec.X * scale; + y0 = edge.Vertex0.Vec.Y * scale; + x1 = edge.Vertex1.Vec.X * scale; + y1 = edge.Vertex1.Vec.Y * scale; + } + + segs.Add(new LineSeg(x0, y0, x1, y1)); + } + + return segs; + } + } +} diff --git a/SourceGen/WpfGui/EditVisualizationSet.xaml b/SourceGen/WpfGui/EditVisualizationSet.xaml index bc2322e..8c8b661 100644 --- a/SourceGen/WpfGui/EditVisualizationSet.xaml +++ b/SourceGen/WpfGui/EditVisualizationSet.xaml @@ -23,7 +23,7 @@ limitations under the License. xmlns:local="clr-namespace:SourceGen.WpfGui" mc:Ignorable="d" Title="Edit Visualization Set" - Width="600" Height="400" ResizeMode="NoResize" + Width="640" Height="480" ResizeMode="NoResize" ShowInTaskbar="False" WindowStartupLocation="CenterOwner" Closing="Window_Closing"> @@ -71,21 +71,21 @@ limitations under the License. - + - - + - - + +