Progress toward wireframe animations

Handle the remaining visualization editor UI controls, except for
the "test" button.  Save/restore wireframe animations in the
project file.  Changed the preview from a 1-pixel-wide line drawn
by a path half the window size to a 2-pixel-wide line drawn by a
path the exact window size.
This commit is contained in:
Andy McFadden 2020-03-08 17:05:08 -07:00
parent 7e92a86ffa
commit b68e39ab6b
7 changed files with 207 additions and 41 deletions

View File

@ -1051,8 +1051,18 @@ namespace SourceGen {
vis = null;
return false;
}
vis = new Visualization(serVis.Tag, serVis.VisGenIdent,
new ReadOnlyDictionary<string, object>(parms));
// We don't store VisWireframeAnimations in a separate area. They're just like
// static Visualizations but with an extra "is animated" parameter set. Check
// for that here and create the correct type.
if (parms.TryGetValue(VisWireframeAnimation.P_IS_ANIMATED, out object objVal) &&
objVal is bool && (bool)objVal) {
vis = new VisWireframeAnimation(serVis.Tag, serVis.VisGenIdent,
new ReadOnlyDictionary<string, object>(parms), null, null);
} else {
vis = new Visualization(serVis.Tag, serVis.VisGenIdent,
new ReadOnlyDictionary<string, object>(parms));
}
return true;
}

View File

@ -42,7 +42,26 @@
},
"UserLabels":{
},
"10":{
"Label":"vertices",
"Value":4106,
"Source":"User",
"Type":"GlobalAddr",
"LabelAnno":"None"},
"44":{
"Label":"edges",
"Value":4140,
"Source":"User",
"Type":"GlobalAddr",
"LabelAnno":"None"},
"101":{
"Label":"faces",
"Value":4197,
"Source":"User",
"Type":"GlobalAddr",
"LabelAnno":"None"}},
"OperandFormats":{
"10":{
@ -248,8 +267,14 @@
"_isPerspective":true,
"_isBfcEnabled":true,
"_eulerRotX":0,
"_eulerRotY":34,
"_eulerRotZ":65}},
"_eulerRotY":21,
"_eulerRotZ":65,
"_isAnimatedWireframe":true,
"_deltaRotX":-6,
"_deltaRotY":30,
"_deltaRotZ":0,
"_frameCount":60,
"_frameDelayMsec":100}},
{
"Tag":"bmp_data",
@ -269,7 +294,7 @@
"Tag":"anim00002c",
"VisGenIdent":"(animation)",
"VisGenParams":{
"_frame-delay-msec":500}}],
"_frameDelayMsec":100}}],
"VisualizationSets":{
"10":{
"Tags":["wf_data",

View File

@ -36,7 +36,7 @@ namespace SourceGen {
/// <summary>
/// Frame delay parameter.
/// </summary>
public const string P_FRAME_DELAY_MSEC_PARAM = "_frame-delay-msec";
public const string P_FRAME_DELAY_MSEC_PARAM = "_frameDelayMsec";
public const string P_FRAME_DELAY_MSEC_PARAM_OLD = "frame-delay-msec";
/// <summary>

View File

@ -32,25 +32,36 @@ namespace SourceGen {
/// <summary>
/// Frame delay parameter.
/// </summary>
public const string P_FRAME_DELAY_MSEC = "_frame-delay-msec";
public const string P_FRAME_DELAY_MSEC = "_frameDelayMsec";
/// <summary>
/// Frame count parameter.
/// </summary>
public const string P_FRAME_COUNT = "_frame-count";
public const string P_FRAME_COUNT = "_frameCount";
public const string P_IS_ANIMATED = "_isAnimatedWireframe";
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 const string P_DELTA_ROT_X = "_deltaRotX";
public const string P_DELTA_ROT_Y = "_deltaRotY";
public const string P_DELTA_ROT_Z = "_deltaRotZ";
private IVisualizationWireframe mVisWire;
public VisWireframeAnimation(string tag, string visGenIdent,
ReadOnlyDictionary<string, object> visGenParams, Visualization oldObj,
IVisualizationWireframe visWire)
: base(tag, visGenIdent, visGenParams, oldObj) {
Debug.Assert(visWire != null);
// visWire may be null when loading from project file
mVisWire = visWire;
}
public override void SetThumbnail(IVisualizationWireframe visWire,
ReadOnlyDictionary<string, object> parms) {
base.SetThumbnail(visWire, parms);
mVisWire = visWire;
}
}

View File

@ -192,7 +192,7 @@ namespace SourceGen {
/// </summary>
/// <param name="visWire">Visualization object.</param>
/// <param name="parms">Visualization parameters.</param>
public void SetThumbnail(IVisualizationWireframe visWire,
public virtual void SetThumbnail(IVisualizationWireframe visWire,
ReadOnlyDictionary<string, object> parms) {
Debug.Assert(visWire != null);
Debug.Assert(parms != null);

View File

@ -134,17 +134,18 @@ limitations under the License.
<Border Grid.Row="1" BorderThickness="1" HorizontalAlignment="Stretch"
BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"
Background="{StaticResource CheckerBackground}">
Background="{StaticResource CheckerBackground}"
SnapsToDevicePixels="True">
<!-- explicit width gets cleared after initial window layout -->
<Grid Name="previewGrid" Width="548" Height="400">
<!-- either the image or the path is shown, not both; background is set to
transparent for image, black for path -->
<Image Name="previewImage" Source="/Res/RedX.png"
RenderOptions.BitmapScalingMode="NearestNeighbor"/>
RenderOptions.BitmapScalingMode="NearestNeighbor"/>
<!-- ClipToBounds="True" -->
<!-- RenderOptions.EdgeMode="Aliased" -->
<Viewbox>
<Path Name="wireframePath" Stroke="White"/>
<Path Name="wireframePath" Stroke="White" StrokeThickness="2"/>
</Viewbox>
</Grid>
</Border>
@ -192,24 +193,24 @@ limitations under the License.
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Grid.ColumnSpan="3" Margin="4" HorizontalAlignment="Stretch"
<Rectangle Grid.Row="0" Grid.ColumnSpan="3" Margin="0,4,0,4" HorizontalAlignment="Stretch"
Fill="LightGray" Height="2"/>
<TextBlock Grid.Column="0" Grid.Row="1" Text="Initial X rotation (deg):" Margin="0,5,0,0"/>
<TextBlock Grid.Column="0" Grid.Row="1" Text="X axis rotation (deg):" Margin="0,5,0,0"/>
<TextBox Grid.Column="1" Grid.Row="1" Margin="4,4,0,0" Width="30" MaxLength="3"
Text="{Binding ElementName=initialXSlider, Path=Value, UpdateSourceTrigger=PropertyChanged}"/>
<Slider Name="initialXSlider" Grid.Column="2" Grid.Row="1" Margin="4,4,0,0" Width="360"
Minimum="0" Maximum="359" TickFrequency="1" IsSnapToTickEnabled="True"
ValueChanged="InitialRotSlider_ValueChanged"/>
<TextBlock Grid.Column="0" Grid.Row="2" Text="Initial Y rotation (deg):" Margin="0,5,0,0"/>
<TextBlock Grid.Column="0" Grid.Row="2" Text="Y axis rotation (deg):" Margin="0,5,0,0"/>
<TextBox Grid.Column="1" Grid.Row="2" Margin="4,4,0,0" Width="30" MaxLength="3"
Text="{Binding ElementName=initialYSlider, Path=Value, UpdateSourceTrigger=PropertyChanged}"/>
<Slider Name="initialYSlider" Grid.Column="2" Grid.Row="2" Margin="4,4,0,0" Width="360"
Minimum="0" Maximum="359" TickFrequency="1" IsSnapToTickEnabled="True"
ValueChanged="InitialRotSlider_ValueChanged"/>
<TextBlock Grid.Column="0" Grid.Row="3" Text="Initial Z rotation (deg):" Margin="0,5,0,0"/>
<TextBlock Grid.Column="0" Grid.Row="3" Text="Z axis rotation (deg):" Margin="0,5,0,0"/>
<TextBox Grid.Column="1" Grid.Row="3" Margin="4,4,0,0" Width="30" MaxLength="3"
Text="{Binding ElementName=initialZSlider, Path=Value, UpdateSourceTrigger=PropertyChanged}"/>
<Slider Name="initialZSlider" Grid.Column="2" Grid.Row="3" Margin="4,4,0,0" Width="360"
@ -222,24 +223,35 @@ limitations under the License.
<!-- remaining items are enabled when "isAnimated" checkbox is checked -->
<TextBlock Grid.Column="0" Grid.Row="5" Grid.ColumnSpan="2" Margin="0,5,0,0"
Text="Rotation per frame:"/>
Text="Rotation per frame:"
IsEnabled="{Binding ElementName=isAnimated, Path=IsChecked}"/>
<StackPanel Grid.Column="1" Grid.Row="5" Grid.ColumnSpan="2" Orientation="Horizontal" Margin="4,4,0,0"
IsEnabled="{Binding ElementName=isAnimated, Path=IsChecked}">
<TextBlock Text="X:" Margin="0,1,0,0"/>
<TextBox Width="40" MaxLength="4" Margin="4,0,0,0"/>
<TextBox Width="40" MaxLength="4" Margin="4,0,0,0"
Text="{Binding RotDeltaX}"/>
<TextBlock Text="Y:" Margin="8,1,0,0"/>
<TextBox Width="40" MaxLength="4" Margin="4,0,0,0"/>
<TextBox Width="40" MaxLength="4" Margin="4,0,0,0"
Text="{Binding RotDeltaY}"/>
<TextBlock Text="Z:" Margin="8,1,0,0"/>
<TextBox Width="40" MaxLength="4" Margin="4,0,0,0"/>
<TextBox Width="40" MaxLength="4" Margin="4,0,0,0"
Text="{Binding RotDeltaZ}"/>
</StackPanel>
<TextBlock Grid.Column="0" Grid.Row="6" Text="Frame count:" Margin="0,5,0,0"/>
<TextBox Grid.Column="1" Grid.Row="6" Width="50" Height="18" Margin="4,4,0,0"/>
<TextBlock Grid.Column="0" Grid.Row="6" Text="Frame count:" Margin="0,5,0,0"
IsEnabled="{Binding ElementName=isAnimated, Path=IsChecked}"/>
<TextBox Grid.Column="1" Grid.Row="6" Width="50" Height="18" Margin="4,4,0,0"
Text="{Binding FrameCount}" MaxLength="4"
IsEnabled="{Binding ElementName=isAnimated, Path=IsChecked}"/>
<Button Grid.Column="2" Grid.Row="6" Width="70" Margin="4,4,0,0" HorizontalAlignment="Left"
Content="Auto" />
Content="Auto" Click="AutoFrameCountButton_Click"
IsEnabled="{Binding ElementName=isAnimated, Path=IsChecked}"/>
<TextBlock Grid.Column="0" Grid.Row="7" Text="Frame delay (msec):" Margin="0,5,0,0"/>
<TextBox Grid.Column="1" Grid.Row="7" Width="50" Margin="4,4,0,0"/>
<TextBlock Grid.Column="0" Grid.Row="7" Text="Frame delay (msec):" Margin="0,5,0,0"
IsEnabled="{Binding ElementName=isAnimated, Path=IsChecked}"/>
<TextBox Grid.Column="1" Grid.Row="7" Width="50" Margin="4,4,0,0"
Text="{Binding FrameDelayMsec}" MaxLength="5"
IsEnabled="{Binding ElementName=isAnimated, Path=IsChecked}"/>
</Grid>
<DockPanel Grid.Column="0" Grid.Row="5" Margin="0,8,0,0" LastChildFill="False">

View File

@ -81,18 +81,6 @@ namespace SourceGen.WpfGui {
}
private bool mIsValid;
public Visibility WireframeCtrlVisibility {
get { return mWireframeCtrlVisibility; }
set { mWireframeCtrlVisibility = value; OnPropertyChanged(); }
}
private Visibility mWireframeCtrlVisibility;
public bool IsWireframeAnimated {
get { return mIsWireframeAnimated; }
set { mIsWireframeAnimated = value; OnPropertyChanged(); }
}
private bool mIsWireframeAnimated;
/// <summary>
/// Visualization tag.
/// </summary>
@ -166,6 +154,54 @@ namespace SourceGen.WpfGui {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#region Wireframe Stuff
// Visibility of the wireframe-specific UI.
public Visibility WireframeCtrlVisibility {
get { return mWireframeCtrlVisibility; }
set { mWireframeCtrlVisibility = value; OnPropertyChanged(); }
}
private Visibility mWireframeCtrlVisibility;
// "Animated" checkbox.
public bool IsWireframeAnimated {
get { return mIsWireframeAnimated; }
set { mIsWireframeAnimated = value; OnPropertyChanged(); }
}
private bool mIsWireframeAnimated;
public int RotDeltaX {
get { return mRotDeltaX; }
set { mRotDeltaX = value; OnPropertyChanged(); }
}
private int mRotDeltaX;
public int RotDeltaY {
get { return mRotDeltaY; }
set { mRotDeltaY = value; OnPropertyChanged(); }
}
private int mRotDeltaY;
public int RotDeltaZ {
get { return mRotDeltaZ; }
set { mRotDeltaZ = value; OnPropertyChanged(); }
}
private int mRotDeltaZ;
public int FrameCount {
get { return mFrameCount; }
set { mFrameCount = value; OnPropertyChanged(); }
}
private int mFrameCount;
public int FrameDelayMsec {
get { return mFrameDelayMsec; }
set { mFrameDelayMsec = value; OnPropertyChanged(); }
}
private int mFrameDelayMsec;
#endregion Wireframe Stuff
private class ScriptSupport : MarshalByRefObject, PluginCommon.IApplication {
private EditVisualization mOuter;
@ -390,6 +426,7 @@ namespace SourceGen.WpfGui {
WireframeCtrlVisibility = includeWire ? Visibility.Visible : Visibility.Collapsed;
if (includeWire) {
// Slider control limits values to [0,359].
int rotX = (int)initialXSlider.Value;
int rotY = (int)initialYSlider.Value;
int rotZ = (int)initialZSlider.Value;
@ -397,6 +434,22 @@ namespace SourceGen.WpfGui {
valueDict.Add(VisWireframeAnimation.P_EULER_ROT_X, rotX);
valueDict.Add(VisWireframeAnimation.P_EULER_ROT_Y, rotY);
valueDict.Add(VisWireframeAnimation.P_EULER_ROT_Z, rotZ);
// Strictly speaking we don't need this, because we use a different object
// type, but this ties into how the object is stored in the project file.
valueDict.Add(VisWireframeAnimation.P_IS_ANIMATED, IsWireframeAnimated);
// These could be any integer value, but the UI limits them to 4 chars, and
// it's all mod 360.
valueDict.Add(VisWireframeAnimation.P_DELTA_ROT_X, RotDeltaX);
valueDict.Add(VisWireframeAnimation.P_DELTA_ROT_Y, RotDeltaY);
valueDict.Add(VisWireframeAnimation.P_DELTA_ROT_Z, RotDeltaZ);
// These aren't strictly checked by the UI, so range-check here.
int fc = (FrameCount >= 1 && FrameCount <= 9999) ? FrameCount : 1;
valueDict.Add(VisWireframeAnimation.P_FRAME_COUNT, fc);
int dly = (FrameDelayMsec >= 1 && FrameDelayMsec <= 999999) ? FrameDelayMsec : 100;
valueDict.Add(VisWireframeAnimation.P_FRAME_DELAY_MSEC, dly);
}
return new ReadOnlyDictionary<string, object>(valueDict);
@ -550,8 +603,9 @@ namespace SourceGen.WpfGui {
} else {
previewGrid.Background = Brushes.Black;
previewImage.Source = Visualization.BLANK_IMAGE;
wireframePath.Data = Visualization.GenerateWireframePath(visWire, parms,
Math.Min(previewImage.ActualWidth, previewImage.ActualHeight) / 2);
double dim = Math.Floor(
Math.Min(previewImage.ActualWidth, previewImage.ActualHeight));
wireframePath.Data = Visualization.GenerateWireframePath(visWire, parms, dim);
BitmapDimensions = "n/a";
mVisObj = visWire;
@ -590,6 +644,26 @@ namespace SourceGen.WpfGui {
VisWireframeAnimation.P_EULER_ROT_Y, 0);
initialZSlider.Value = Util.GetFromObjDict(mOrigVis.VisGenParams,
VisWireframeAnimation.P_EULER_ROT_Z, 0);
// Set this according to the object type, rather than P_IS_ANIMATED. The two
// should always be in sync. This should help make it more obvious if they
// aren't.
IsWireframeAnimated = (mOrigVis is VisWireframeAnimation);
// This should make it *really* obvious.
Debug.Assert(IsWireframeAnimated == Util.GetFromObjDict(mOrigVis.VisGenParams,
VisWireframeAnimation.P_IS_ANIMATED, false));
RotDeltaX = Util.GetFromObjDict(mOrigVis.VisGenParams,
VisWireframeAnimation.P_DELTA_ROT_X, 0);
RotDeltaY = Util.GetFromObjDict(mOrigVis.VisGenParams,
VisWireframeAnimation.P_DELTA_ROT_Y, 0);
RotDeltaZ = Util.GetFromObjDict(mOrigVis.VisGenParams,
VisWireframeAnimation.P_DELTA_ROT_Z, 0);
FrameCount = Util.GetFromObjDict(mOrigVis.VisGenParams,
VisWireframeAnimation.P_FRAME_COUNT, 1);
FrameDelayMsec = Util.GetFromObjDict(mOrigVis.VisGenParams,
VisWireframeAnimation.P_FRAME_DELAY_MSEC, 100);
}
UpdateControls();
@ -617,6 +691,40 @@ namespace SourceGen.WpfGui {
private void TestAnim_Click(object sender, RoutedEventArgs e) {
Debug.WriteLine("TEST!");
}
/// <summary>
/// Sets the number of frames in the animation based on how many incremental
/// rotations are required to return the shape to its initial orientation. The
/// count will always be between 1 and 360.
/// </summary>
/// <remarks>
/// There might be a clever way to do this with math, but this is pretty simple.
/// </remarks>
private void AutoFrameCountButton_Click(object sender, RoutedEventArgs e) {
int xr, yr, zr;
int xstart = xr = (int)initialXSlider.Value;
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++;
if (xr == xstart && yr == ystart && zr == zstart) {
break;
}
}
}
FrameCount = count;
}
}
/// <summary>