1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-05-31 22:41:37 +00:00

Progress on wireframe visualization

Added some more plumbing.  Updated visualization set edit dialog,
which now does word-wrapping correctly in the buttons.  Added Alt+V
as the hotkey for Create/Edit Visualization Set, which allows you
to double-tap it to leap into the visualization editor.

Experimented with Path drawing, which looks like it could do just
what we need.

Also, show the file size in KB in the code/data/junk breakdown at the
bottom of the window.  (Technically it's KiB, but that looked funny.)
This commit is contained in:
Andy McFadden 2020-03-01 18:18:31 -08:00
parent e8870c30e8
commit 38ca9005c4
11 changed files with 185 additions and 35 deletions

View File

@ -20,9 +20,27 @@ using System.Diagnostics;
namespace PluginCommon {
/// <summary>
/// Wireframe mesh with optional backface normals, for use with visualization generators.
/// Call the various functions to add data, then call Validate() to check for broken
/// references.
/// </summary>
[Serializable]
public class VisWireframe : IVisualizationWireframe {
public static VisParamDescr Param_IsPerspective(string uiLabel, bool defaultVal) {
return new VisParamDescr(uiLabel, "_isPerspective", 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);
}
public static VisParamDescr Param_EulerX(string uiLabel, int defaultVal) {
return new VisParamDescr(uiLabel, "_eulerRotX", 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);
}
public static VisParamDescr Param_EulerZ(string uiLabel, int defaultVal) {
return new VisParamDescr(uiLabel, "_eulerRotZ", typeof(int), 0, 359, 0, defaultVal);
}
private List<float> mVerticesX = new List<float>();
private List<float> mVerticesY = new List<float>();
private List<float> mVerticesZ = new List<float>();
@ -56,14 +74,15 @@ namespace PluginCommon {
}
/// <summary>
/// Adds the edge to the list.
/// Adds an edge to the list. The referenced vertices do not need to be defined
/// before calling.
/// </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);
Debug.Assert(index0 >= 0);
Debug.Assert(index1 >= 0);
mEdges.Add(new IntPair(index0, index1));
return mEdges.Count - 1;
}
@ -84,24 +103,26 @@ namespace PluginCommon {
}
/// <summary>
/// Marks a vertex's visibility as being tied to the specified face. The face does
/// not need to be in the list yet.
/// Marks a vertex's visibility as being tied to the specified face. The vertices and
/// faces being referenced do not need to exist 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);
Debug.Assert(vertexIndex >= 0);
Debug.Assert(faceIndex >= 0);
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.
/// Marks an edge's visibility as being tied to the specified face. The edges and
/// faces being referenced do not need to exist 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);
Debug.Assert(edgeIndex >= 0);
Debug.Assert(faceIndex >= 0);
mEdgeFaces.Add(new IntPair(edgeIndex, faceIndex));
}

View File

@ -697,7 +697,7 @@ namespace SourceGen {
float dataPerc = (mProject.ByteCounts.DataByteCount * 100.0f) / total;
float junkPerc = (mProject.ByteCounts.JunkByteCount * 100.0f) / total;
mMainWin.ByteCountText = string.Format(Res.Strings.STATUS_BYTE_COUNT_FMT,
codePerc, dataPerc, junkPerc);
total / 1024.0f, codePerc, dataPerc, junkPerc);
}
#endregion Init and settings

View File

@ -161,7 +161,7 @@ limitations under the License.
<system:String x:Key="str_ScanC64ScreenCode">C64 Screen Code</system:String>
<system:String x:Key="str_SetupSystemSummaryFmt">{1} CPU @ {2} MHz</system:String>
<system:String x:Key="str_ShowCol">Show</system:String>
<system:String x:Key="str_StatusByteCountFmt">{0:F1}% code, {1:F1}% data, {2:F1}% junk</system:String>
<system:String x:Key="str_StatusByteCountFmt">{0:F1}KB: {1:F1}% code, {2:F1}% data, {3:F1}% junk</system:String>
<system:String x:Key="str_StatusReady">Ready</system:String>
<system:String x:Key="str_StrVfyDciMixedData">DCI string has mixed data</system:String>
<system:String x:Key="str_StrVfyDciNotTerminated">DCI string not terminated</system:String>

View File

@ -41,6 +41,12 @@ namespace WireframeTest {
new VisParamDescr[] {
new VisParamDescr("File offset (hex)",
P_OFFSET, typeof(int), 0, 0x00ffffff, VisParamDescr.SpecialMode.Offset, 0),
// These are interpreted by the main app.
VisWireframe.Param_EulerX("Euler rotation X", 0),
VisWireframe.Param_EulerY("Euler rotation Y", 0),
VisWireframe.Param_EulerZ("Euler rotation Z", 0),
VisWireframe.Param_IsPerspective("Perspective projection", true),
}),
};

View File

@ -17,7 +17,8 @@
"SmartPlpHandling":true},
"PlatformSymbolFileIdentifiers":[],
"ExtensionScriptFileIdentifiers":["PROJ:VisWireframeTest.cs"],
"ExtensionScriptFileIdentifiers":["PROJ:VisWireframeTest.cs",
"RT:Apple/VisHiRes.cs"],
"ProjectSyms":{
}},
@ -239,7 +240,30 @@
"LvTables":{
},
"Visualizations":[],
"Visualizations":[{
"Tag":"wf_data",
"VisGenIdent":"wireframe-test",
"VisGenParams":{
"offset":10,
"_eulerRotX":0,
"_eulerRotY":0,
"_eulerRotZ":0,
"_isPerspective":true}},
{
"Tag":"bmp_data",
"VisGenIdent":"apple2-hi-res-bitmap",
"VisGenParams":{
"offset":10,
"byteWidth":3,
"height":11,
"colStride":0,
"rowStride":0,
"isColor":true,
"isFirstOdd":false,
"isHighBitFlipped":false}}],
"VisualizationAnimations":[],
"VisualizationSets":{
}}
"10":{
"Tags":["wf_data",
"bmp_data"]}}}

View File

@ -18,6 +18,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
@ -99,6 +100,7 @@ namespace SourceGen {
VisualizationAnimation.GenerateAnimOverlayImage();
internal static readonly BitmapSource BLANK_IMAGE = GenerateBlankImage();
internal static readonly BitmapSource BLACK_IMAGE = GenerateBlackImage();
/// <summary>
/// Serial number, for reference from other Visualization objects. Not serialized.
@ -106,9 +108,10 @@ namespace SourceGen {
/// <remarks>
/// This value is only valid in the current session. It exists because animations
/// need to refer to other Visualization objects, and doing so by Tag gets sticky
/// if a tag gets renamed. We need a way to uniquely identify a reference to a
/// if a Tag gets renamed. We need a way to uniquely identify a reference to a
/// Visualization that persists across Tag renames and other edits. When the objects
/// are serialized to the project file we just output the tags.
/// are serialized to the project file we don't include the serial, and just reference
/// by Tag.
/// </remarks>
public int SerialNumber { get; private set; }
@ -191,7 +194,8 @@ namespace SourceGen {
}
/// <summary>
/// Converts an IVisualization2d to a BitmapSource for display.
/// Converts an IVisualization2d to a BitmapSource for display. The bitmap will be
/// the same size as the original content.
/// </summary>
public static BitmapSource ConvertToBitmapSource(IVisualization2d vis2d) {
// Create indexed color palette.
@ -218,12 +222,69 @@ namespace SourceGen {
return image;
}
/// <summary>
/// Generates a BitmapSource from IVisualizationWireframe data. Useful for thumbnails
/// and GIF exports.
/// </summary>
/// <param name="visWire">Visualization data.</param>
/// <param name="parms">Parameter set, for rotations and render options.</param>
/// <param name="width">Output bitmap width.</param>
/// <param name="height">Output bitmap height.</param>
/// <returns></returns>
public static BitmapSource GenerateWireframeImage(IVisualizationWireframe visWire,
ReadOnlyDictionary<string, object> parms, double width, double height) {
// TODO(xyzzy): need to get path into a bitmap for thumbnails / GIFs...; call
// GenerateWireframePath and then render the path
// https://stackoverflow.com/a/23582564/294248
Debug.WriteLine("Render " + visWire + " at " + width + "x" + height);
return null;
}
/// <summary>
/// Generates a WPF path from IVisualizationWireframe data.
/// </summary>
public static GeometryGroup GenerateWireframePath(IVisualizationWireframe visWire,
ReadOnlyDictionary<string, object> parms, double scale) {
GeometryGroup geo = new GeometryGroup();
// This establishes the geometry bounds. It's a zero-length line segment, so
// nothing is actually drawn.
Debug.WriteLine("using scale=" + scale);
Point corner = new Point(scale + 1, scale + 1);
geo.Children.Add(new LineGeometry(corner, corner));
// TODO(xyzzy): render
geo.Children.Add(new LineGeometry(new Point(6, 6), new Point(197, 197)));
geo.Children.Add(new LineGeometry(new Point(6, 197), new Point(197, 6)));
return geo;
}
/// <summary>
/// Returns a bitmap with a single transparent pixel.
/// </summary>
private static BitmapSource GenerateBlankImage() {
RenderTargetBitmap bmp = new RenderTargetBitmap(1, 1, 96.0, 96.0,
PixelFormats.Pbgra32);
return bmp;
}
/// <summary>
/// Returns a bitmap with a single black pixel.
/// </summary>
private static BitmapSource GenerateBlackImage() {
BitmapPalette palette = new BitmapPalette(new List<Color> { Colors.Black });
BitmapSource image = BitmapSource.Create(
1,
1,
96.0,
96.0,
PixelFormats.Indexed8,
palette,
new byte[] { 0 },
1);
return image;
}
public override string ToString() {
return "[Vis: " + Tag + " (" + VisGenIdent + ") count=" + VisGenParams.Count + "]";

View File

@ -134,8 +134,13 @@ limitations under the License.
<Border Grid.Row="1" BorderThickness="1" HorizontalAlignment="Stretch"
BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"
Background="{StaticResource CheckerBackground}">
<Image Name="previewImage" Width="400" Height="400" Source="/Res/RedX.png"
RenderOptions.BitmapScalingMode="NearestNeighbor"/>
<Grid Name="previewGrid" Width="400" Height="400">
<Image Name="previewImage" Source="/Res/RedX.png"
RenderOptions.BitmapScalingMode="NearestNeighbor"/>
<Viewbox>
<Path Name="wireframePath" Stroke="White"/>
</Viewbox>
</Grid>
</Border>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">

View File

@ -302,8 +302,8 @@ namespace SourceGen.WpfGui {
ClearValue(SizeToContentProperty);
SetValue(MinWidthProperty, this.Width);
SetValue(MinHeightProperty, this.Height);
previewImage.ClearValue(WidthProperty);
previewImage.ClearValue(HeightProperty);
previewGrid.ClearValue(WidthProperty);
previewGrid.ClearValue(HeightProperty);
tagTextBox.SelectAll();
tagTextBox.Focus();
@ -450,13 +450,25 @@ namespace SourceGen.WpfGui {
// Invoke the plugin.
PluginErrMessage = string.Empty;
IVisualization2d vis2d;
IVisualization2d vis2d = null;
IVisualizationWireframe visWire = null;
ReadOnlyDictionary<string, object> parms = CreateVisGenParams();
try {
IPlugin_Visualizer plugin =
(IPlugin_Visualizer)mProject.GetPlugin(item.ScriptIdent);
vis2d = plugin.Generate2d(item.VisDescriptor, CreateVisGenParams());
if (vis2d == null) {
Debug.WriteLine("Vis generator returned null");
if (item.VisDescriptor.VisualizationType == VisDescr.VisType.Bitmap) {
vis2d = plugin.Generate2d(item.VisDescriptor, parms);
if (vis2d == null) {
Debug.WriteLine("Vis2d generator returned null");
}
} else if (item.VisDescriptor.VisualizationType == VisDescr.VisType.Wireframe) {
IPlugin_Visualizer_v2 plugin2 = (IPlugin_Visualizer_v2)plugin;
visWire = plugin2.GenerateWireframe(item.VisDescriptor, parms);
if (visWire == null) {
Debug.WriteLine("VisWire generator returned null");
}
} else {
Debug.Assert(false);
}
} catch (Exception ex) {
Debug.WriteLine("Vis generation failed: " + ex);
@ -465,7 +477,7 @@ namespace SourceGen.WpfGui {
LastPluginMessage = ex.Message;
}
}
if (vis2d == null) {
if (vis2d == null && visWire == null) {
previewImage.Source = sBadParamsImage;
if (!string.IsNullOrEmpty(LastPluginMessage)) {
// Report the last message we got as an error.
@ -474,10 +486,16 @@ namespace SourceGen.WpfGui {
PluginErrMessage = (string)FindResource("str_VisGenFailed");
}
IsValid = false;
} else {
} else if (vis2d != null) {
previewImage.Source = Visualization.ConvertToBitmapSource(vis2d);
wireframePath.Data = new GeometryGroup();
BitmapDimensions = string.Format("{0}x{1}",
previewImage.Source.Width, previewImage.Source.Height);
} else {
previewImage.Source = Visualization.BLACK_IMAGE;
wireframePath.Data = Visualization.GenerateWireframePath(visWire, parms,
previewImage.ActualWidth / 2);
BitmapDimensions = "n/a";
}
}

View File

@ -85,15 +85,22 @@ limitations under the License.
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Tag" Width="176" Binding="{Binding Tag}"/>
<DataGridTextColumn Header="Visualization Generator" Width="210" Binding="{Binding VisGenIdent}"/>
<DataGridTextColumn Header="Visualization Generator" Width="200" Binding="{Binding VisGenIdent}"/>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Column="1" Grid.Row="1">
<Button Width="110" Height="36" Margin="4" Content="New _Bitmap..."
IsEnabled="{Binding HasVisPlugins}" Click="NewBitmapButton_Click"/>
<Button Width="110" Margin="4" Content="New Bitmap&#x0a;_Animation..."
Click="NewBitmapAnimationButton_Click"/>
<Button Width="120" Height="36" Margin="4"
IsEnabled="{Binding HasVisPlugins}" Click="NewVisualizationButton_Click">
<AccessText Text="New _Visualization..." TextWrapping="Wrap" TextAlignment="Center"/>
</Button>
<Button Width="120" Margin="4" Click="NewBitmapAnimationButton_Click">
<AccessText Text="New _Bitmap Animation..." TextWrapping="Wrap" TextAlignment="Center"/>
</Button>
<Button Width="120" Margin="4"
IsEnabled="{Binding HasVisPlugins}" Click="NewWireframeAnimationButton_Click">
<AccessText Text="New _Wireframe Animation..." TextWrapping="Wrap" TextAlignment="Center"/>
</Button>
<Button Width="110" Margin="4,20,4,4" Content="_Edit..."
IsEnabled="{Binding IsEditEnabled}" Click="EditButton_Click"/>

View File

@ -181,7 +181,7 @@ namespace SourceGen.WpfGui {
EditSelectedItem();
}
private void NewBitmapButton_Click(object sender, RoutedEventArgs e) {
private void NewVisualizationButton_Click(object sender, RoutedEventArgs e) {
EditVisualization dlg = new EditVisualization(this, mProject, mFormatter, mOffset,
CreateEditedSetList(), null);
if (dlg.ShowDialog() != true) {
@ -193,6 +193,10 @@ namespace SourceGen.WpfGui {
okButton.Focus();
}
private void NewWireframeAnimationButton_Click(object sender, RoutedEventArgs e) {
// TODO(xyzzy)
}
private void NewBitmapAnimationButton_Click(object sender, RoutedEventArgs e) {
EditBitmapAnimation dlg = new EditBitmapAnimation(this, mOffset,
CreateEditedSetList(), null);

View File

@ -107,7 +107,11 @@ limitations under the License.
</RoutedUICommand>
<RoutedUICommand x:Key="EditProjectSymbolCmd" Text="Edit Project Symbol..."/>
<RoutedUICommand x:Key="EditStatusFlagsCmd" Text="Override Status Flags..."/>
<RoutedUICommand x:Key="EditVisualizationSetCmd" Text="Create/Edit Visualization Set..."/>
<RoutedUICommand x:Key="EditVisualizationSetCmd" Text="Create/Edit Visualization Set...">
<RoutedUICommand.InputGestures>
<KeyGesture>Alt+V</KeyGesture>
</RoutedUICommand.InputGestures>
</RoutedUICommand>
<RoutedUICommand x:Key="ExitCmd" Text="Exit"/>
<RoutedUICommand x:Key="ExportCmd" Text="Export..."/>
<RoutedUICommand x:Key="FindNextCmd" Text="Find Next">
@ -480,7 +484,7 @@ limitations under the License.
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding StatusBarText, FallbackValue=Ready}"/>
<TextBlock Grid.Column="1" HorizontalAlignment="Center"
Text="{Binding ByteCountText, FallbackValue=30% code; 60% data; 10% junk}"/>
Text="{Binding ByteCountText, FallbackValue=100KB: 30.0% code; 60.0% data; 10.0% junk}"/>
<Button Grid.Column="2" Margin="4,0,20,0" Padding="4,0,4,0"
Content="{Binding MessageStatusText, FallbackValue=3 messages (1 warning/error)}"
Visibility="{Binding Path=CodeListVisibility}"