/* * 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 { // Surface normal. public Vector3 Normal { get; private set; } // One vertex on the face, for BFC. public Vertex Vert { get; set; } // Flag set during BFC calculation. public bool IsVisible { get; set; } public Face(double x, double y, double z) { Normal = new Vector3(x, y, z); Normal.Normalize(); // not necessary, but easier to read in debug output IsVisible = true; } } 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; } 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; } /// /// 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, int eulerX, int eulerY, int eulerZ) { List segs = new List(mEdges.Count); bool doPersp = Util.GetFromObjDict(parms, VisWireframe.P_IS_PERSPECTIVE, false); bool doBfc = Util.GetFromObjDict(parms, VisWireframe.P_IS_BFC_ENABLED, false); // Camera Z coordinate adjustment, used to control how perspective projections // appear. The larger the value, the farther the object appears to be. Very // large values approximate an orthographic projection. const double zadj = 3.0; // Scale coordinate values to [-1,1]. double scale = 1.0 / mBigMag; if (doPersp) { // objects closer to camera are bigger; reduce scale slightly scale = (scale * zadj) / (zadj + 0.5); } Matrix44 rotMat = new Matrix44(); rotMat.SetRotationEuler(eulerX, eulerY, eulerZ); if (doBfc) { // Mark faces as visible or not. This is determined with the surface normal, // rather than by checking whether a transformed triangle is clockwise. foreach (Face face in mFaces) { // Transform the surface normal. Vector3 rotNorm = rotMat.Multiply(face.Normal); if (doPersp) { // Transform one vertex to get a vector from the camera to the // surface. We want (V0 - C), where C is the camera; since we're // at the origin, we just need -C. if (face.Vert == null) { Debug.WriteLine("GLITCH: no vertex for face"); face.IsVisible = true; continue; } Vector3 camVec = rotMat.Multiply(face.Vert.Vec); camVec.Multiply(-scale); // scale to [-1,1] and negate to get -C camVec.Z += zadj; // translate // Now compute the dot product of the camera vector. double dot = Vector3.Dot(camVec, rotNorm); face.IsVisible = (dot >= 0); //Debug.WriteLine(string.Format( // "Face {0} vis={1,-5} dot={2,-8:N2}: camVec={3} rotNorm={4}", // index++, face.IsVisible, dot, camVec, rotNorm)); } else { // For orthographic projection, the camera is essentially looking // down the Z axis at every X,Y, so we can trivially check the // value of Z in the transformed normal. face.IsVisible = (rotNorm.Z >= 0); } } } foreach (Edge edge in mEdges) { if (doBfc) { // To be visible, vertices and edges must either not specify any // faces, or must specify a visible face. if (!IsVertexVisible(edge.Vertex0) || !IsVertexVisible(edge.Vertex1) || !IsEdgeVisible(edge)) { continue; } } Vector3 trv0 = rotMat.Multiply(edge.Vertex0.Vec); Vector3 trv1 = rotMat.Multiply(edge.Vertex1.Vec); double x0, y0, x1, y1; if (doPersp) { // +Z on the shape is closer to the viewer, so we negate it here double z0 = -trv0.Z * scale; double z1 = -trv1.Z * scale; x0 = (trv0.X * scale * zadj) / (zadj + z0); y0 = (trv0.Y * scale * zadj) / (zadj + z0); x1 = (trv1.X * scale * zadj) / (zadj + z1); y1 = (trv1.Y * scale * zadj) / (zadj + z1); } else { x0 = trv0.X * scale; y0 = trv0.Y * scale; x1 = trv1.X * scale; y1 = trv1.Y * scale; } segs.Add(new LineSeg(x0, y0, x1, y1)); } return segs; } private bool IsVertexVisible(Vertex vert) { if (vert.Faces.Count == 0) { return true; } foreach (Face face in vert.Faces) { if (face.IsVisible) { return true; } } return false; } private bool IsEdgeVisible(Edge edg) { if (edg.Faces.Count == 0) { return true; } foreach (Face face in edg.Faces) { if (face.IsVisible) { return true; } } return false; } } }