/// <summary> /// Constructor. Mostly pass-through, but we want to set the overlay image. /// </summary> public VisWireframeAnimation(string tag, string visGenIdent, ReadOnlyDictionary <string, object> visGenParams, Visualization oldObj, WireframeObject wireObj) : base(tag, visGenIdent, visGenParams, oldObj) { // wireObj may be null when loading from project file mWireObj = wireObj; OverlayImage = ANIM_OVERLAY_IMAGE; }
/// <summary> /// Generates WPF Path geometry from IVisualizationWireframe data. Line widths get /// scaled if the output area is larger or smaller than the path bounds, so this scales /// coordinates so they fit within the box. /// </summary> /// <param name="visWire">Visualization data.</param> /// <param name="dim">Width/height to use for path area.</param> /// <param name="parms">Visualization parameters.</param> public static GeometryGroup GenerateWireframePath(WireframeObject wireObj, double dim, ReadOnlyDictionary <string, object> parms) { int eulerX = Util.GetFromObjDict(parms, VisWireframeAnimation.P_EULER_ROT_X, 0); int eulerY = Util.GetFromObjDict(parms, VisWireframeAnimation.P_EULER_ROT_Y, 0); 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)); }
/// <summary> /// Generates a BitmapSource from IVisualizationWireframe data. Useful for thumbnails /// and GIF exports. /// </summary> /// <param name="visWire">Visualization data.</param> /// <param name="dim">Output bitmap dimension (width and height).</param> /// <param name="parms">Parameter set, for rotations and render options.</param> /// <returns>Rendered bitmap.</returns> public static BitmapSource GenerateWireframeImage(WireframeObject wireObj, double dim, ReadOnlyDictionary <string, object> parms) { int eulerX = Util.GetFromObjDict(parms, VisWireframeAnimation.P_EULER_ROT_X, 0); int eulerY = Util.GetFromObjDict(parms, VisWireframeAnimation.P_EULER_ROT_Y, 0); 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); bool doRecenter = Util.GetFromObjDict(parms, VisWireframe.P_IS_RECENTERED, true); return(GenerateWireframeImage(wireObj, dim, eulerX, eulerY, eulerZ, doPersp, doBfc, doRecenter)); }
/// <summary> /// Updates the thumbnail. /// </summary> /// <remarks> /// We override it because this is our first opportunity to capture the /// wireframe object reference if the object was created during project /// file loading. /// </remarks> /// <param name="visWire">Reference to wireframe data generated by plugin.</param> /// <param name="parms">Render parameters.</param> public override void SetThumbnail(IVisualizationWireframe visWire, ReadOnlyDictionary <string, object> parms) { base.SetThumbnail(visWire, parms); if (visWire == null) { // Thumbnail cache is being cleared. Throw out the wireframe object too. mWireObj = null; } else { mWireObj = WireframeObject.Create(visWire); } }
/// <summary> /// Updates the cached thumbnail image. /// </summary> /// <param name="visWire">Visualization object, or null to clear the thumbnail.</param> /// <param name="parms">Visualization parameters.</param> public virtual void SetThumbnail(IVisualizationWireframe visWire, ReadOnlyDictionary <string, object> parms) { if (visWire == null) { CachedImage = BROKEN_IMAGE; } else { Debug.Assert(parms != null); WireframeObject wireObj = WireframeObject.Create(visWire); CachedImage = GenerateWireframeImage(wireObj, THUMBNAIL_DIM, parms); } Debug.Assert(CachedImage.IsFrozen); }
/// <summary> /// Generates WPF Path geometry from IVisualizationWireframe data. Line widths get /// scaled if the output area is larger or smaller than the path bounds, so this scales /// 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, 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 // outside the window (visible or not based on ClipToBounds). // // If you draw a line from (1,1 to 4,1), the line's length will appear to // be (4 - 1) = 3. It touches four pixels -- the end point is not exclusive -- // but the filled area is only three, because the thickness doesn't extend the // line's length, and the line stops at the coordinate at the center of the pixel. // You're not drawing N pixels, you're drawing from one coordinate point to another. // If you have a window of size 8x8, and you draw from 0,0 to 7,0, the line will // extend for half a line-thickness off the top, 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 // produce anything. // // It's possible to clean up the edges by adding 0.5 to all coordinate values. // This turns out to be important for another reason: a line from (1,1) to (9,1) // shows up as a double-wide half-bright line, while a line from (1.5,1.5) to // (9.5,1.5) is drawn as a single-wide full-brightness line. This is because of // the anti-aliasing. Anti-aliasing can be disabled, but the lines look much // nicer with it enabled. // // The path has an axis-aligned bounding box that covers the pixel centers. If we // want a path-drawn mesh 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 plot an invisible point 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(); // 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)); // 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, 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, // and add +0.5 to make thumbnail-size bitmaps 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)); } return(geo); }
/// <summary> /// Generates a BitmapSource from IVisualizationWireframe data. Useful for thumbnails /// and GIF exports. /// </summary> public static BitmapSource GenerateWireframeImage(WireframeObject wireObj, 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. return(BROKEN_IMAGE); } // Generate the path geometry. GeometryGroup geo = GenerateWireframePath(wireObj, dim, eulerX, eulerY, eulerZ, doPersp, doBfc, doRecenter); // Render geometry to bitmap -- https://stackoverflow.com/a/869767/294248 Rect bounds = geo.GetRenderBounds(null); //Debug.WriteLine("RenderWF dim=" + dim + " bounds=" + bounds + ": " + wireObj); // Create bitmap. RenderTargetBitmap bitmap = new RenderTargetBitmap( (int)dim, (int)dim, 96, 96, PixelFormats.Pbgra32); //RenderOptions.SetEdgeMode(bitmap, EdgeMode.Aliased); <-- no apparent effect DrawingVisual dv = new DrawingVisual(); using (DrawingContext dc = dv.RenderOpen()) { dc.DrawRectangle(Brushes.Black, null, new Rect(0, 0, bounds.Width, bounds.Height)); Pen pen = new Pen(Brushes.White, 1.0); dc.DrawGeometry(null, pen, geo); } bitmap.Render(dv); #if false // Old way: render Path to bitmap -- https://stackoverflow.com/a/23582564/294248 // Clear the bitmap to black. (Is there an easier way?) GeometryGroup bkgnd = new GeometryGroup(); bkgnd.Children.Add(new RectangleGeometry(new Rect(0, 0, bounds.Width, bounds.Height))); Path path = new Path(); path.Data = bkgnd; path.Stroke = path.Fill = Brushes.Black; path.Measure(bounds.Size); path.Arrange(bounds); bitmap.Render(path); path = new Path(); path.Data = geo; path.Stroke = Brushes.White; path.Measure(bounds.Size); path.Arrange(bounds); bitmap.Render(path); #endif bitmap.Freeze(); return(bitmap); }
/// <summary> /// Creates a new object from a wireframe visualization. /// </summary> /// <param name="visWire">Visualization object.</param> /// <returns>New object, or null if visualization data fails validation.</returns> public static WireframeObject Create(IVisualizationWireframe visWire) { if (!visWire.Validate(out string msg)) { // Should not be here -- visualizer should have checked validation and // reported an error. Debug.WriteLine("Wireframe validation failed: " + msg); return(null); } 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) { float[] normalsY = visWire.GetNormalsY(); float[] normalsZ = visWire.GetNormalsZ(); 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(); int[] excludedVertices = visWire.GetExcludedVertices(); // 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))); } int[] points = visWire.GetPoints(); for (int i = 0; i < points.Length; 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(); int[] excludedEdges = visWire.GetExcludedEdges(); 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; //} 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(); 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; //} Face face = wireObj.mFaces[findex]; wireObj.mVertices[vindex].Faces.Add(face); if (face.Vert == null) { face.Vert = wireObj.mVertices[vindex]; } } 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; //} Face face = wireObj.mFaces[findex]; wireObj.mEdges[eindex].Faces.Add(face); if (face.Vert == null) { face.Vert = wireObj.mEdges[eindex].Vertex0; } } // // 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++) { 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; } } // Avoid divide-by-zero. if (bigMag == 0) { Debug.WriteLine("NOTE: wireframe magnitude was zero"); bigMag = 1; } if (bigMagRc == 0) { bigMagRc = 1; } wireObj.mBigMag = bigMag; wireObj.mBigMagRc = bigMagRc; return(wireObj); }
/// <summary> /// Creates a new object from a wireframe visualization. /// </summary> /// <param name="visWire">Visualization object.</param> /// <returns>New object.</returns> 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(); int[] excludedVertices = visWire.GetExcludedVertices(); 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], HasIndex(excludedVertices, i))); } IntPair[] edges = visWire.GetEdges(); int[] excludedEdges = visWire.GetExcludedEdges(); 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], HasIndex(excludedEdges, i))); } 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); } Face face = wireObj.mFaces[findex]; wireObj.mVertices[vindex].Faces.Add(face); if (face.Vert == null) { face.Vert = wireObj.mVertices[vindex]; } } 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); } Face face = wireObj.mFaces[findex]; wireObj.mEdges[eindex].Faces.Add(face); if (face.Vert == null) { face.Vert = wireObj.mEdges[eindex].Vertex0; } } // // 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); }