1
0
mirror of https://github.com/fadden/6502bench.git synced 2025-01-27 01:29:48 +00:00

Implement wireframe animation

You can view the animated wireframe with the "test" button in the
visualization editor, and export it as an animated GIF.
This commit is contained in:
Andy McFadden 2020-03-09 13:48:30 -07:00
parent b68e39ab6b
commit 8c01f383e5
12 changed files with 291 additions and 51 deletions

View File

@ -808,7 +808,6 @@ namespace SourceGen {
Debug.Assert(false); // not expected
}
}
#if false
// try feeding the animated GIF into our GIF unpacker
using (MemoryStream ms = new MemoryStream()) {
@ -828,8 +827,25 @@ namespace SourceGen {
}
} catch (Exception ex) {
// TODO: add an error report
Debug.WriteLine("Error creating animated GIF file '" + pathName + "': " +
ex.Message);
Debug.WriteLine("Error creating animated GIF file '" + pathName +
"': " + ex.Message);
dispWidth = dispHeight = 1;
}
} else if (vis is VisWireframeAnimation) {
AnimatedGifEncoder encoder = new AnimatedGifEncoder();
((VisWireframeAnimation)vis).EncodeGif(encoder, IMAGE_SIZE);
// Create new or replace existing image file.
fileName += "_ani.gif";
string pathName = Path.Combine(mImageDirPath, fileName);
try {
using (FileStream stream = new FileStream(pathName, FileMode.Create)) {
encoder.Save(stream, out dispWidth, out dispHeight);
}
} catch (Exception ex) {
// TODO: add an error report
Debug.WriteLine("Error creating animated WF GIF file '" + pathName +
"': " + ex.Message);
dispWidth = dispHeight = 1;
}
} else {

View File

@ -267,13 +267,13 @@
"_isPerspective":true,
"_isBfcEnabled":true,
"_eulerRotX":0,
"_eulerRotY":21,
"_eulerRotZ":65,
"_eulerRotY":0,
"_eulerRotZ":0,
"_isAnimatedWireframe":true,
"_deltaRotX":-6,
"_deltaRotY":30,
"_deltaRotX":3,
"_deltaRotY":6,
"_deltaRotZ":0,
"_frameCount":60,
"_frameCount":120,
"_frameDelayMsec":100}},
{
@ -291,14 +291,14 @@
"VisualizationAnimations":[{
"Tags":["wf_data",
"bmp_data"],
"Tag":"anim00002c",
"Tag":"ugly flash",
"VisGenIdent":"(animation)",
"VisGenParams":{
"_frameDelayMsec":100}}],
"_frameDelayMsec":1000}}],
"VisualizationSets":{
"10":{
"Tags":["wf_data",
"bmp_data"]},
"44":{
"Tags":["anim00002c"]}}}
"Tags":["ugly flash"]}}}

View File

@ -207,6 +207,9 @@
<Compile Include="UndoableChange.cs" />
<Compile Include="DisplayListSelection.cs" />
<Compile Include="WeakSymbolRef.cs" />
<Compile Include="WpfGui\ShowWireframeAnimation.xaml.cs">
<DependentUpon>ShowWireframeAnimation.xaml</DependentUpon>
</Compile>
<Compile Include="XrefSet.cs" />
</ItemGroup>
<ItemGroup>
@ -418,6 +421,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="WpfGui\ShowWireframeAnimation.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Asm65\Asm65.csproj">

View File

@ -17,7 +17,9 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using CommonWPF;
using PluginCommon;
namespace SourceGen {
@ -51,18 +53,61 @@ namespace SourceGen {
private IVisualizationWireframe mVisWire;
/// <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,
IVisualizationWireframe visWire)
: base(tag, visGenIdent, visGenParams, oldObj) {
// visWire may be null when loading from project file
mVisWire = visWire;
OverlayImage = ANIM_OVERLAY_IMAGE;
}
/// <summary>
/// Updates the thumbnail.
/// </summary>
/// <remarks>
/// We override it because this is our first opportunity to capture the
/// IVisualizationWireframe reference when 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);
mVisWire = visWire;
}
/// <summary>
/// Generates an animated GIF from a series of frames.
/// </summary>
/// <param name="encoder">GIF encoder.</param>
/// <param name="dim">Output dimensions.</param>
public void EncodeGif(AnimatedGifEncoder encoder, double dim) {
int curX = Util.GetFromObjDict(VisGenParams, P_EULER_ROT_X, 0);
int curY = Util.GetFromObjDict(VisGenParams, P_EULER_ROT_Y, 0);
int curZ = Util.GetFromObjDict(VisGenParams, P_EULER_ROT_Z, 0);
int deltaX = Util.GetFromObjDict(VisGenParams, P_DELTA_ROT_X, 0);
int deltaY = Util.GetFromObjDict(VisGenParams, P_DELTA_ROT_Y, 0);
int deltaZ = Util.GetFromObjDict(VisGenParams, P_DELTA_ROT_Z, 0);
int frameCount = Util.GetFromObjDict(VisGenParams, P_FRAME_COUNT, 1);
int frameDelayMsec = Util.GetFromObjDict(VisGenParams, P_FRAME_DELAY_MSEC, 100);
bool doPersp = Util.GetFromObjDict(VisGenParams, VisWireframe.P_IS_PERSPECTIVE, true);
bool doBfc = Util.GetFromObjDict(VisGenParams, VisWireframe.P_IS_BFC_ENABLED, false);
for (int frame = 0; frame < frameCount; frame++) {
BitmapSource bs = GenerateWireframeImage(mVisWire, dim,
curX, curY, curZ, doPersp, doBfc);
encoder.AddFrame(BitmapFrame.Create(bs), frameDelayMsec);
curX = (curX + 360 + deltaX) % 360;
curY = (curY + 360 + deltaY) % 360;
curZ = (curZ + 360 + deltaZ) % 360;
}
}
}
}

View File

@ -196,7 +196,7 @@ namespace SourceGen {
ReadOnlyDictionary<string, object> parms) {
Debug.Assert(visWire != null);
Debug.Assert(parms != null);
CachedImage = GenerateWireframeImage(visWire, parms, THUMBNAIL_DIM);
CachedImage = GenerateWireframeImage(visWire, THUMBNAIL_DIM, parms);
}
/// <summary>
@ -254,13 +254,28 @@ namespace SourceGen {
/// and GIF exports.
/// </summary>
/// <param name="visWire">Visualization data.</param>
/// <param name="parms">Parameter set, for rotations and render options.</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(IVisualizationWireframe visWire,
ReadOnlyDictionary<string, object> parms, double dim) {
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, false);
bool doBfc = Util.GetFromObjDict(parms, VisWireframe.P_IS_BFC_ENABLED, false);
return GenerateWireframeImage(visWire, dim, eulerX, eulerY, eulerZ, doPersp, doBfc);
}
/// <summary>
/// Generates a BitmapSource from IVisualizationWireframe data. Useful for thumbnails
/// and GIF exports.
/// </summary>
public static BitmapSource GenerateWireframeImage(IVisualizationWireframe visWire,
double dim, int eulerX, int eulerY, int eulerZ, bool doPersp, bool doBfc) {
// Generate the path geometry.
GeometryGroup geo = GenerateWireframePath(visWire, parms, dim);
GeometryGroup geo = GenerateWireframePath(visWire, dim, eulerX, eulerY, eulerZ,
doPersp, doBfc);
// Render Path to bitmap -- https://stackoverflow.com/a/23582564/294248
Rect bounds = geo.GetRenderBounds(null);
@ -302,14 +317,25 @@ namespace SourceGen {
/// coordinates so they fit within the box.
/// </summary>
/// <param name="visWire">Visualization data.</param>
/// <param name="parms">Visualization parameters.</param>
/// <param name="dim">Width/height to use for path area.</param>
/// <param name="parms">Visualization parameters.</param>
public static GeometryGroup GenerateWireframePath(IVisualizationWireframe visWire,
ReadOnlyDictionary<string, object> parms, double dim) {
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, false);
bool doBfc = Util.GetFromObjDict(parms, VisWireframe.P_IS_BFC_ENABLED, false);
return GenerateWireframePath(visWire, dim, eulerX, eulerY, eulerZ, doPersp, doBfc);
}
/// <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(IVisualizationWireframe visWire,
double dim, int eulerX, int eulerY, int eulerZ, bool doPersp, bool doBfc) {
// WPF path drawing is based on a system where a pixel is drawn at the center
// of the coordinate, and integer coordinates start at the top left edge. If
// you draw a pixel at (0,0), most of the pixel will be outside the window
@ -352,7 +378,8 @@ namespace SourceGen {
// Generate a list of clip-space line segments. Coordinate values are in the
// range [-1,1], with +X to the right and +Y upward.
WireframeObject wireObj = WireframeObject.Create(visWire);
List<WireframeObject.LineSeg> segs = wireObj.Generate(parms, eulerX, eulerY, eulerZ);
List<WireframeObject.LineSeg> segs = wireObj.Generate(eulerX, eulerY, eulerZ,
doPersp, doBfc);
// 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,

View File

@ -206,14 +206,16 @@ namespace SourceGen {
/// 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
/// <param name="eulerX">Rotation about X axis.</params>
/// <param name="eulerY">Rotation about Y axis.</params>
/// <param name="eulerZ">Rotation about Z axis.</params>
/// <param name="doPersp">Perspective or othographic projection?</param>
/// <param name="doBfc">Perform backface culling?</param>
/// <returns>List a of line segments, which could be empty if backface culling
/// was especially successful.</returns>
public List<LineSeg> Generate(ReadOnlyDictionary<string, object> parms,
int eulerX, int eulerY, int eulerZ) {
public List<LineSeg> Generate(int eulerX, int eulerY, int eulerZ,
bool doPersp, bool doBfc) {
List<LineSeg> segs = new List<LineSeg>(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

View File

@ -255,8 +255,8 @@ limitations under the License.
</Grid>
<DockPanel Grid.Column="0" Grid.Row="5" Margin="0,8,0,0" LastChildFill="False">
<Button DockPanel.Dock="Left" Content="Test Animation" Padding="4,0" Click="TestAnim_Click"
Visibility="{Binding WireframeCtrlVisibility}"
<Button DockPanel.Dock="Left" Content="_Test Animation..." Width="110"
Click="TestAnim_Click" Visibility="{Binding WireframeCtrlVisibility}"
IsEnabled="{Binding ElementName=isAnimated, Path=IsChecked}"/>
<Button DockPanel.Dock="Right" Content="Cancel" Width="70" Margin="8,0,0,0" IsCancel="True"/>
<Button DockPanel.Dock="Right" Grid.Column="1" Content="OK" Width="70"

View File

@ -52,6 +52,7 @@ namespace SourceGen.WpfGui {
private SortedList<int, VisualizationSet> mEditedList;
private Visualization mOrigVis;
// IVisualization2d or IVisualizationWireframe
public object mVisObj;
/// <summary>
@ -391,7 +392,7 @@ namespace SourceGen.WpfGui {
Debug.Assert(mVisObj is IVisualizationWireframe);
NewVis.CachedImage =
Visualization.GenerateWireframeImage((IVisualizationWireframe)mVisObj,
valueDict, Visualization.THUMBNAIL_DIM);
Visualization.THUMBNAIL_DIM, valueDict);
} else {
Debug.Assert(mVisObj is IVisualization2d);
NewVis.CachedImage =
@ -605,7 +606,7 @@ namespace SourceGen.WpfGui {
previewImage.Source = Visualization.BLANK_IMAGE;
double dim = Math.Floor(
Math.Min(previewImage.ActualWidth, previewImage.ActualHeight));
wireframePath.Data = Visualization.GenerateWireframePath(visWire, parms, dim);
wireframePath.Data = Visualization.GenerateWireframePath(visWire, dim, parms);
BitmapDimensions = "n/a";
mVisObj = visWire;
@ -689,7 +690,9 @@ namespace SourceGen.WpfGui {
}
private void TestAnim_Click(object sender, RoutedEventArgs e) {
Debug.WriteLine("TEST!");
ShowWireframeAnimation dlg = new ShowWireframeAnimation(this, (IVisualizationWireframe)mVisObj,
CreateVisGenParams(true));
dlg.ShowDialog();
}
/// <summary>
@ -706,20 +709,15 @@ namespace SourceGen.WpfGui {
int ystart = yr = (int)initialYSlider.Value;
int zstart = zr = (int)initialZSlider.Value;
int count;
if (RotDeltaX == 0 && RotDeltaY == 0 && RotDeltaZ == 0) {
count = 1;
} else {
count = 0;
while (count < 360) {
xr = (xr + 360 + RotDeltaX) % 360;
yr = (yr + 360 + RotDeltaY) % 360;
zr = (zr + 360 + RotDeltaZ) % 360;
count++;
int count = 0;
while (count < 360) {
xr = (xr + 360 + RotDeltaX) % 360;
yr = (yr + 360 + RotDeltaY) % 360;
zr = (zr + 360 + RotDeltaZ) % 360;
count++;
if (xr == xstart && yr == ystart && zr == zstart) {
break;
}
if (xr == xstart && yr == ystart && zr == zstart) {
break;
}
}

View File

@ -97,10 +97,6 @@ limitations under the License.
<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

@ -193,10 +193,6 @@ 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

@ -0,0 +1,48 @@
<!--
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.
-->
<Window x:Class="SourceGen.WpfGui.ShowWireframeAnimation"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SourceGen.WpfGui"
mc:Ignorable="d"
Title="Wireframe Animation Test"
Width="400" Height="400" MinWidth="80" MinHeight="150" ResizeMode="CanResizeWithGrip"
ShowInTaskbar="False" WindowStartupLocation="CenterOwner"
ContentRendered="Window_ContentRendered"
Closing="Window_Closing">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Name="testBorder" Grid.Row="0" BorderThickness="1" HorizontalAlignment="Stretch"
BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"
Background="Black" SnapsToDevicePixels="True">
<Viewbox>
<Path Name="wireframePath" Stroke="White" StrokeThickness="2"/>
</Viewbox>
</Border>
<Button Grid.Column="0" Grid.Row="1" Content="Close" Width="70" Margin="0,4,0,0"
IsCancel="True" HorizontalAlignment="Right"/>
</Grid>
</Window>

View File

@ -0,0 +1,105 @@
/*
* 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 System.ComponentModel;
using System.Windows;
using System.Windows.Threading;
using PluginCommon;
namespace SourceGen.WpfGui {
/// <summary>
/// Test window for wireframe animations.
/// </summary>
public partial class ShowWireframeAnimation : Window {
/// <summary>
/// Dispatcher-linked timer object.
/// </summary>
private DispatcherTimer mTimer;
IVisualizationWireframe mVisWire;
private int mFrameCount;
private int mInitialX, mInitialY, mInitialZ;
private int mDeltaX, mDeltaY, mDeltaZ;
private bool mDoPersp, mDoBfc;
private int mCurX, mCurY, mCurZ;
private int mCurFrame;
public ShowWireframeAnimation(Window owner, IVisualizationWireframe visWire,
ReadOnlyDictionary<string, object> parms) {
InitializeComponent();
Owner = owner;
mVisWire = visWire;
mCurX = mInitialX = Util.GetFromObjDict(parms, VisWireframeAnimation.P_EULER_ROT_X, 0);
mCurY = mInitialY = Util.GetFromObjDict(parms, VisWireframeAnimation.P_EULER_ROT_Y, 0);
mCurZ = mInitialZ = Util.GetFromObjDict(parms, VisWireframeAnimation.P_EULER_ROT_Z, 0);
mDeltaX = Util.GetFromObjDict(parms, VisWireframeAnimation.P_DELTA_ROT_X, 0);
mDeltaY = Util.GetFromObjDict(parms, VisWireframeAnimation.P_DELTA_ROT_Y, 0);
mDeltaZ = Util.GetFromObjDict(parms, VisWireframeAnimation.P_DELTA_ROT_Z, 0);
mFrameCount = Util.GetFromObjDict(parms, VisWireframeAnimation.P_FRAME_COUNT, 1);
mDoPersp = Util.GetFromObjDict(parms, VisWireframe.P_IS_PERSPECTIVE, true);
mDoBfc = Util.GetFromObjDict(parms, VisWireframe.P_IS_BFC_ENABLED, false);
int intervalMsec = Util.GetFromObjDict(parms,
VisWireframeAnimation.P_FRAME_DELAY_MSEC, 100);
mCurFrame = 0;
mTimer = new DispatcherTimer(DispatcherPriority.Render);
mTimer.Interval = TimeSpan.FromMilliseconds(intervalMsec);
mTimer.Tick += Tick;
}
private void Window_ContentRendered(object sender, EventArgs e) {
//Debug.WriteLine("INITIAL: " + testViewBox.ActualWidth + "x" +
// testViewBox.ActualHeight);
Tick(null, null); // show something immediately
mTimer.Start();
}
private void Window_Closing(object sender, CancelEventArgs e) {
mTimer.Stop();
}
private void Tick(object sender, EventArgs e) {
mCurFrame++;
if (mCurFrame == mFrameCount) {
// reset
mCurX = mInitialX;
mCurY = mInitialY;
mCurZ = mInitialZ;
mCurFrame = 0;
} else {
mCurX = (mCurX + 360 + mDeltaX) % 360;
mCurY = (mCurY + 360 + mDeltaY) % 360;
mCurZ = (mCurZ + 360 + mDeltaZ) % 360;
}
// We use the dimensions of the Border surrounding the ViewBox, rather than the
// ViewBox itself, because on the first iteration the ViewBox has a size of zero.
double dim = Math.Floor(Math.Min(testBorder.ActualWidth, testBorder.ActualHeight));
wireframePath.Data = Visualization.GenerateWireframePath(mVisWire, dim,
mCurX, mCurY, mCurZ, mDoPersp, mDoBfc);
}
}
}