Checkpoint work on wireframe visualization

Defined interfaces and added a test case.
This commit is contained in:
Andy McFadden 2020-02-29 18:30:19 -08:00
parent 4155d254c2
commit e8870c30e8
7 changed files with 728 additions and 4 deletions

View File

@ -122,14 +122,16 @@ namespace CommonWPF {
}
//
// Step 2: determine the size of the largest image. This will become the logical
// size of the animated GIF.
// Step 2: determine the dimensions of the largest image. This will become the
// logical size of the animated GIF.
//
// TODO(maybe): We have an opportunity to replace all of the local color tables with a
// single global color table. This is only possible if all of the local tables are
// identical and the transparency values in the GCE also match up. (Well, it's
// otherwise *possible*, but we'd need to decode, update palettes and pixels, and
// re-encode.)
// TODO(maybe): add an arg to Save() that causes it to use the first bitmap's
// palette as the global palette.
//
foreach (UnpackedGif gif in gifs) {
//gif.DebugDump();

View File

@ -149,7 +149,7 @@ namespace PluginCommon {
/// <summary>
/// Executes the specified visualization generator with the supplied parameters.
/// </summary>
/// <param name="descr">VisGen identifier.</param>
/// <param name="descr">Visualization generator descriptor.</param>
/// <param name="parms">Parameter set.</param>
/// <returns>2D visualization object reference, or null if something went
/// wrong (unknown ident, bad parameters, etc). By convention, an error
@ -157,6 +157,22 @@ namespace PluginCommon {
IVisualization2d Generate2d(VisDescr descr, ReadOnlyDictionary<string, object> parms);
}
/// <summary>
/// Version 2 of the visualizer interface. Adds wireframe objects.
/// </summary>
public interface IPlugin_Visualizer_v2 : IPlugin_Visualizer {
/// <summary>
/// Executes the specified visualization generator with the supplied parameters.
/// </summary>
/// <param name="descr">Visualization generator descriptor.</param>
/// <param name="parms">Parameter set.</param>
/// <returns>Wireframe visualization object reference, or null if something went
/// wrong (unknown ident, bad parameters, etc). By convention, an error
/// message is reported through the IApplication ReportError interface.</returns>
IVisualizationWireframe GenerateWireframe(VisDescr descr,
ReadOnlyDictionary<string, object> parms);
}
/// <summary>
/// Visualization generator descriptor. IPlugin_Visualizer instances publish a list of
/// these to tell the application about the generators it supports.
@ -178,6 +194,7 @@ namespace PluginCommon {
public enum VisType {
Unknown = 0,
Bitmap, // 2D bitmap
Wireframe, // wireframe mesh
}
/// <summary>
@ -302,12 +319,59 @@ namespace PluginCommon {
/// direct-color images.
/// Do not modify.
/// </summary>
/// <returns></returns>
int[] GetPalette();
// TODO(maybe): report pixel aspect ratio?
}
/// <summary>
/// Holds raw vertex/edge/normal data collected from a 2D or 3D wireframe mesh. We use
/// a typical right-handed coordinate system (Z comes out of the screen), with the
/// expectation that the mesh will be presented such that the object is right-side up
/// and facing toward the viewer (nose toward +Z).
///
/// All objects will have vertices and edges. Face normals are optional.
/// </summary>
/// <remarks>
/// The face-normal stuff is designed specifically for Elite, which is one of the very
/// few games to use backface removal.
///
/// We favor multiple arrays over compound objects for this interface to avoid having
/// to define those at the plugin interface level.
///
/// TODO(maybe): specify colors for edges. Not widely used?
/// </remarks>
public interface IVisualizationWireframe {
// Each function returns the specified data. Do not modify.
float[] GetVerticesX();
float[] GetVerticesY();
float[] GetVerticesZ();
IntPair[] GetEdges();
float[] GetNormalsX();
float[] GetNormalsY();
float[] GetNormalsZ();
IntPair[] GetVertexFaces();
IntPair[] GetEdgeFaces();
}
/// <summary>
/// Represents a pair of integers. Immutable.
/// </summary>
[Serializable]
public struct IntPair {
public int Val0 { get; private set; }
public int Val1 { get; private set; }
public IntPair(int val0, int val1) {
Val0 = val0;
Val1 = val1;
}
}
/// <summary>
/// Interfaces provided by the application for use by plugins. An IApplication instance
/// is passed to the plugin as an argument to Prepare().

View File

@ -0,0 +1,206 @@
/*
* 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.Diagnostics;
namespace PluginCommon {
/// <summary>
/// Wireframe mesh with optional backface normals, for use with visualization generators.
/// </summary>
[Serializable]
public class VisWireframe : IVisualizationWireframe {
private List<float> mVerticesX = new List<float>();
private List<float> mVerticesY = new List<float>();
private List<float> mVerticesZ = new List<float>();
private List<IntPair> mEdges = new List<IntPair>();
private List<float> mNormalsX = new List<float>();
private List<float> mNormalsY = new List<float>();
private List<float> mNormalsZ = new List<float>();
private List<IntPair> mVertexFaces = new List<IntPair>();
private List<IntPair> mEdgeFaces = new List<IntPair>();
/// <summary>
/// Constructor. Nothing much to do.
/// </summary>
public VisWireframe() { }
/// <summary>
/// Adds the vertex to the list.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="z">Z coordinate.</param>
/// <returns>Vertex index. Indices start at zero and count up.</returns>
public int AddVertex(float x, float y, float z) {
mVerticesX.Add(x);
mVerticesY.Add(y);
mVerticesZ.Add(z);
return mVerticesX.Count - 1;
}
/// <summary>
/// Adds the edge to the list.
/// </summary>
/// <param name="index0">Index of first vertex.</param>
/// <param name="index1">Index of second vertex.</param>
/// <returns>Edge index. Indices start at zero and count up.</returns>
public int AddEdge(int index0, int index1) {
Debug.Assert(index0 >= 0 && index0 < mVerticesX.Count);
Debug.Assert(index1 >= 0 && index1 < mVerticesX.Count);
mEdges.Add(new IntPair(index0, index1));
return mEdges.Count - 1;
}
/// <summary>
/// Adds the face normal to the list.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="z">Z coordinate.</param>
/// <returns>Face index. Indices start at zero and count up.</returns>
public int AddFaceNormal(float x, float y, float z) {
Debug.Assert(x != 0.0f || y != 0.0f || z != 0.0f); // no zero-length normals
mNormalsX.Add(x);
mNormalsY.Add(y);
mNormalsZ.Add(z);
return mNormalsX.Count - 1;
}
/// <summary>
/// Marks a vertex's visibility as being tied to the specified face. The face does
/// not need to be in the list yet.
/// </summary>
/// <param name="vertexIndex">Index of vertex.</param>
/// <param name="faceIndex">Index of face.</param>
public void AddVertexFace(int vertexIndex, int faceIndex) {
Debug.Assert(vertexIndex >= 0 && vertexIndex < mVerticesX.Count);
mVertexFaces.Add(new IntPair(vertexIndex, faceIndex));
}
/// <summary>
/// Marks an edge's visibility as being tied to the specified face. The face does
/// not need to be in the list yet.
/// </summary>
/// <param name="edgeIndex">Index of edge.</param>
/// <param name="faceIndex">Index of face.</param>
public void AddEdgeFace(int edgeIndex, int faceIndex) {
Debug.Assert(edgeIndex >= 0 && edgeIndex < mEdges.Count);
mEdgeFaces.Add(new IntPair(edgeIndex, faceIndex));
}
/// <summary>
/// Verifies that the various references by index are valid.
/// </summary>
/// <param name="msg">Failure detail.</param>
/// <returns>True if everything looks valid.</returns>
public bool Validate(out string msg) {
int vertexCount = mVerticesX.Count;
int faceCount = mNormalsX.Count;
int edgeCount = mEdges.Count;
// check edges
foreach (IntPair ip in mEdges) {
if (ip.Val0 < 0 || ip.Val0 >= vertexCount ||
ip.Val1 < 0 || ip.Val1 >= vertexCount) {
msg = "invalid edge";
return false;
}
}
// check vertex-faces
foreach (IntPair ip in mVertexFaces) {
if (ip.Val0 < 0 || ip.Val0 >= vertexCount ||
ip.Val1 < 0 || ip.Val1 >= faceCount) {
msg = "invalid vertex-face";
return false;
}
}
// check edge-faces
foreach (IntPair ip in mVertexFaces) {
if (ip.Val0 < 0 || ip.Val0 >= edgeCount ||
ip.Val1 < 0 || ip.Val1 >= faceCount) {
msg = "invalid edge-face";
return false;
}
}
// check face normals
for (int i = 0; i < mNormalsX.Count; i++) {
if (mNormalsX[i] == 0.0f && mNormalsY[i] == 0.0f && mNormalsZ[i] == 0.0f) {
msg = "zero-length normal";
return false;
}
}
msg = string.Empty;
return true;
}
//
// IVisualizationWireframe implementation.
//
public float[] GetVerticesX() {
return mVerticesX.ToArray();
}
public float[] GetVerticesY() {
return mVerticesY.ToArray();
}
public float[] GetVerticesZ() {
return mVerticesZ.ToArray();
}
public IntPair[] GetEdges() {
return mEdges.ToArray();
}
public float[] GetNormalsX() {
return mNormalsX.ToArray();
}
public float[] GetNormalsY() {
return mNormalsY.ToArray();
}
public float[] GetNormalsZ() {
return mNormalsZ.ToArray();
}
public IntPair[] GetVertexFaces() {
return mVertexFaces.ToArray();
}
public IntPair[] GetEdgeFaces() {
return mEdgeFaces.ToArray();
}
public override string ToString() {
return "[VisWireframe: " + mVerticesX.Count + " vertices, " +
mEdges.Count + " edges, " +
mNormalsX.Count + " faces, " +
mVertexFaces.Count + " vfaces, " +
mEdgeFaces.Count + " efaces]";
}
}
}

View File

@ -0,0 +1,149 @@
/*
* 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 WireframeTest {
/// <summary>
/// Visualizer for wireframe test data.
/// </remarks>
public class VisWireframeTest : MarshalByRefObject, IPlugin, IPlugin_Visualizer_v2 {
// IPlugin
public string Identifier {
get { return "Wireframe Test Data Visualizer"; }
}
private IApplication mAppRef;
private byte[] mFileData;
// Visualization identifiers; DO NOT change or projects that use them will break.
private const string VIS_GEN_WF_TEST = "wireframe-test";
private const string P_OFFSET = "offset";
// Visualization descriptors.
private VisDescr[] mDescriptors = new VisDescr[] {
new VisDescr(VIS_GEN_WF_TEST, "Wireframe Test Data", VisDescr.VisType.Wireframe,
new VisParamDescr[] {
new VisParamDescr("File offset (hex)",
P_OFFSET, typeof(int), 0, 0x00ffffff, VisParamDescr.SpecialMode.Offset, 0),
}),
};
// IPlugin
public void Prepare(IApplication appRef, byte[] fileData, AddressTranslate addrTrans) {
mAppRef = appRef;
mFileData = fileData;
}
// IPlugin
public void Unprepare() {
mAppRef = null;
mFileData = 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_WF_TEST:
return GenerateWireframe(parms);
default:
mAppRef.ReportError("Unknown ident " + descr.Ident);
return null;
}
}
private IVisualizationWireframe GenerateWireframe(ReadOnlyDictionary<string, object> parms) {
int offset = Util.GetFromObjDict(parms, P_OFFSET, 0);
if (offset < 0 || offset >= mFileData.Length) {
// should be caught by editor
mAppRef.ReportError("Invalid parameter");
return null;
}
VisWireframe vw = new VisWireframe();
const sbyte END_MARKER = -128; // 0x80
try {
while (true) {
int vx = (sbyte)mFileData[offset++];
if (vx == END_MARKER) {
break;
}
int vy = (sbyte)mFileData[offset++];
int vz = (sbyte)mFileData[offset++];
vw.AddVertex(vx, vy, vz);
}
while (true) {
int v0 = (sbyte)mFileData[offset++];
if (v0 == END_MARKER) {
break;
}
int v1 = mFileData[offset++];
int f0 = mFileData[offset++];
int f1 = mFileData[offset++];
int edge = vw.AddEdge(v0, v1);
vw.AddEdgeFace(edge, f0);
vw.AddEdgeFace(edge, f1);
}
while (true) {
int nx = (sbyte)mFileData[offset++];
if (nx == END_MARKER) {
break;
}
int ny = (sbyte)mFileData[offset++];
int nz = (sbyte)mFileData[offset++];
vw.AddFaceNormal(nx, ny, nz);
}
} 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;
}
}
}

Binary file not shown.

View File

@ -0,0 +1,58 @@
; Copyright 2020 faddenSoft. All Rights Reserved.
; See the LICENSE.txt file for distribution terms (Apache 2.0).
;
; Assembler: Merlin 32
org $1000
lda vertices
lda edges
lda faces
rts
; List of vertices (X,Y,Z).
vertices
dfb -32,32,32 ;0
dfb -32,-32,32 ;1
dfb 32,-32,32 ;2
dfb 32,32,32 ;3
dfb -32,32,-32 ;4
dfb -32,-32,-32 ;5
dfb 32,-32,-32 ;6
dfb 32,32,-32 ;7
; put a decoration on the front face; should look like a '7'
dfb -20,-20,32 ;8
dfb 20,20,32 ;9
dfb 10,20,32 ;10
dfb $80
; List of edges (vertex0, vertex1, face0, face1).
edges
dfb 0,1, 0,1 ;0
dfb 1,2, 0,3 ;1
dfb 2,3, 0,4 ;2
dfb 3,0, 0,2 ;3
dfb 4,5, 1,5 ;4
dfb 5,6, 1,3 ;5
dfb 6,7, 1,4 ;6
dfb 7,4, 1,2 ;7
dfb 0,4, 2,5 ;8
dfb 1,5, 3,5 ;9
dfb 2,6, 3,4 ;10
dfb 3,7, 2,4 ;11
dfb 8,9, 0,0 ;12
dfb 9,10,0,0 ;13
dfb $80
; List of faces (surface normal X,Y,Z).
faces
dfb 0,0,1 ;0 front
dfb 0,0,-1 ;1 back
dfb 0,1,0 ;2 top
dfb 0,-1,0 ;3 bottom
dfb 1,0,0 ;4 right
dfb -1,0,0 ;5 left
dfb $80

View File

@ -0,0 +1,245 @@
### 6502bench SourceGen dis65 v1.0 ###
{
"_ContentVersion":3,
"FileDataLength":120,
"FileDataCrc32":1432202158,
"ProjectProps":{
"CpuName":"6502",
"IncludeUndocumentedInstr":false,
"TwoByteBrk":false,
"EntryFlags":32702671,
"AutoLabelStyle":"Simple",
"AnalysisParams":{
"AnalyzeUncategorizedData":true,
"DefaultTextScanMode":"LowHighAscii",
"MinCharsForString":4,
"SeekNearbyTargets":true,
"SmartPlpHandling":true},
"PlatformSymbolFileIdentifiers":[],
"ExtensionScriptFileIdentifiers":["PROJ:VisWireframeTest.cs"],
"ProjectSyms":{
}},
"AddressMap":[{
"Offset":0,
"Addr":4096}],
"TypeHints":[{
"Low":0,
"High":0,
"Hint":"Code"}],
"StatusFlagOverrides":{
},
"Comments":{
},
"LongComments":{
},
"Notes":{
},
"UserLabels":{
},
"OperandFormats":{
"10":{
"Length":3,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"13":{
"Length":3,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"16":{
"Length":3,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"19":{
"Length":3,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"22":{
"Length":3,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"25":{
"Length":3,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"28":{
"Length":3,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"31":{
"Length":3,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"34":{
"Length":3,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"37":{
"Length":3,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"40":{
"Length":3,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"43":{
"Length":1,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"44":{
"Length":4,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"48":{
"Length":4,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"52":{
"Length":4,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"56":{
"Length":4,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"60":{
"Length":4,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"64":{
"Length":4,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"68":{
"Length":4,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"72":{
"Length":4,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"76":{
"Length":4,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"80":{
"Length":4,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"84":{
"Length":4,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"88":{
"Length":4,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"92":{
"Length":4,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"96":{
"Length":4,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"101":{
"Length":3,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"104":{
"Length":3,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"107":{
"Length":3,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"110":{
"Length":3,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"113":{
"Length":3,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null},
"116":{
"Length":3,
"Format":"Dense",
"SubFormat":"None",
"SymbolRef":null}},
"LvTables":{
},
"Visualizations":[],
"VisualizationAnimations":[],
"VisualizationSets":{
}}