1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-11-25 14:34:27 +00:00

Implement basic wireframe rendering

We extract the data from the wireframe visualization, perform a
trivial transform, and display it.  The perspective vs.
orthographic flag in the parameters is respected.  (No rotation or
backface removal yet.)

Also, increased the thumbnail sizes in the visualization set editor
list from 48x48 to 64x64, because the nearest-pixel-scaled 48x48
looks nasty when used for wireframes.
This commit is contained in:
Andy McFadden 2020-03-03 19:29:15 -08:00
parent bd0b20dc2f
commit eec847d5f1
8 changed files with 353 additions and 51 deletions

55
CommonUtil/Vector3.cs Normal file
View File

@ -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 {
/// <summary>
/// Simple 3-element column vector.
/// </summary>
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;
}
}
}

View File

@ -342,7 +342,7 @@ namespace PluginCommon {
/// TODO(maybe): specify colors for edges. Not widely used?
/// </remarks>
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();

View File

@ -25,20 +25,26 @@ namespace PluginCommon {
/// </summary>
[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<float> mVerticesX = new List<float>();
@ -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 ||

View File

@ -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) {

View File

@ -105,6 +105,7 @@
<Compile Include="Visualization.cs" />
<Compile Include="VisualizationAnimation.cs" />
<Compile Include="VisualizationSet.cs" />
<Compile Include="WireframeObject.cs" />
<Compile Include="WpfGui\AboutBox.xaml.cs">
<DependentUpon>AboutBox.xaml</DependentUpon>
</Compile>

View File

@ -172,13 +172,16 @@ namespace SourceGen {
}
}
/// <summary>
/// Updates the cached thumbnail image.
/// </summary>
/// <param name="visWire">Visualization object.</param>
/// <param name="parms">Visualization parameters.</param>
public void SetThumbnail(IVisualizationWireframe visWire,
ReadOnlyDictionary<string, object> 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);
}
/// <summary>
@ -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<WireframeObject.LineSeg> 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;
}

View File

@ -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 {
/// <summary>
/// Renders a wireframe visualization, generating a collection of line segments in clip space.
/// </summary>
public class WireframeObject {
/// <summary>
/// Line segment.
/// </summary>
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<Face> 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<Face>();
}
}
private class Edge {
public Vertex Vertex0 { get; private set; }
public Vertex Vertex1 { get; private set; }
public List<Face> Faces { get; private set; }
public Edge(Vertex v0, Vertex v1) {
Vertex0 = v0;
Vertex1 = v1;
Faces = new List<Face>();
}
}
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<Vertex> mVertices = new List<Vertex>();
private List<Edge> mEdges = new List<Edge>();
private List<Face> mFaces = new List<Face>();
private double mBigMag = -1.0;
// private constructor; use Create()
private WireframeObject() { }
/// <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();
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;
}
/// <summary>
/// Generates a list of line segments for the wireframe data and the specified
/// parameters.
/// </summary>
/// <param name="parms">Visualization parameters.</param>
/// <returns>List of line segments, which could be empty if backface removal
/// was especially successful.</returns>
public List<LineSeg> Generate(ReadOnlyDictionary<string, object> parms) {
List<LineSeg> segs = new List<LineSeg>(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;
}
}
}

View File

@ -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.
</DataGrid.Resources>
<!-- ItemsSource is a list of Visualization -->
<DataGrid.Columns>
<DataGridTemplateColumn Header="Img" Width="56">
<DataGridTemplateColumn Header="Img" Width="68">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border BorderThickness="0" Background="{StaticResource BitmapBackground}">
<Grid>
<Image Source="{Binding CachedImage}" Width="48" Height="48"
<Image Source="{Binding CachedImage}" Width="64" Height="64"
RenderOptions.BitmapScalingMode="NearestNeighbor"/>
<Image Source="{Binding OverlayImage}" Width="48" Height="48"/>
<Image Source="{Binding OverlayImage}" Width="64" Height="64"/>
</Grid>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Tag" Width="176" Binding="{Binding Tag}"/>
<DataGridTextColumn Header="Visualization Generator" Width="200" Binding="{Binding VisGenIdent}"/>
<DataGridTextColumn Header="Tag" Width="180" Binding="{Binding Tag}"/>
<DataGridTextColumn Header="Visualization Generator" Width="224" Binding="{Binding VisGenIdent}"/>
</DataGrid.Columns>
</DataGrid>