Skip to content

Commit 1167deb

Browse files
authored
Change animation updater to absolute timestamp based. (helix-toolkit#1931)
* Change animation updater to absolute timestamp based. Fix multiple bugs in node based animation. * Update demos. * Adds warning logging if number of bone index is more than 4 per vertex.
1 parent fedec73 commit 1167deb

20 files changed

+522
-346
lines changed

Source/Examples/SharpDX.Core/WinFormsTest/CoreTestApp.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,8 @@ private void Viewport_OnStartRendering(object sender, EventArgs e)
375375

376376
if (options.PlayAnimation && options.AnimationUpdater != null)
377377
{
378-
options.AnimationUpdater.Update(Stopwatch.GetTimestamp(), Stopwatch.Frequency);
378+
var elapsed = Stopwatch.GetTimestamp() - options.InitTimeStamp;
379+
options.AnimationUpdater.Update(elapsed, Stopwatch.Frequency);
379380
}
380381
#if TESTADDREMOVE
381382
if (groupSphere.Items.Count > 0 && !isAddingNode)

Source/Examples/SharpDX.Core/WinFormsTest/SceneUI.cs

+1
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ private static void DrawAnimations(ref ViewportOptions options)
227227
ImGui.Text($"Animations: {animationNames.Length}");
228228
if(ImGui.Combo(" ", ref currentSelectedAnimation, animationNames, animationNames.Length))
229229
{
230+
options.InitTimeStamp = 0;
230231
if(currentSelectedAnimation >= 0 && currentSelectedAnimation < animationNames.Length)
231232
{
232233
options.AnimationUpdater = animationUpdaters[currentSelectedAnimation];

Source/Examples/SharpDX.Core/WinFormsTest/ViewportControl.cs

+1
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ public struct ViewportOptions
2626
public IViewport3DX Viewport;
2727
public bool ShowEnvironmentMap;
2828
public bool EnableDpiScale;
29+
public long InitTimeStamp;
2930
}
3031
}

Source/Examples/WPF.SharpDX/BoneSkinDemo/MainViewModel.cs

+20-13
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ public bool ShowWireframe
2727
{
2828
set
2929
{
30-
if(SetValue(ref showWireframe, value))
30+
if (SetValue(ref showWireframe, value))
3131
{
32-
foreach(var m in boneSkinNodes)
32+
foreach (var m in boneSkinNodes)
3333
{
3434
m.RenderWireframe = value;
3535
}
@@ -46,9 +46,9 @@ public bool ShowSkeleton
4646
{
4747
set
4848
{
49-
if(SetValue(ref showSkeleton, value))
49+
if (SetValue(ref showSkeleton, value))
5050
{
51-
foreach(var m in skeletonNodes)
51+
foreach (var m in skeletonNodes)
5252
{
5353
m.Visible = value;
5454
}
@@ -83,7 +83,7 @@ public string SelectedAnimation
8383
{
8484
set
8585
{
86-
if(SetValue(ref selectedAnimation, value))
86+
if (SetValue(ref selectedAnimation, value))
8787
{
8888
reset = true;
8989
var curr = scene.Animations.Where(x => x.Name == value).FirstOrDefault();
@@ -99,7 +99,7 @@ public AnimationRepeatMode SelectedRepeatMode
9999
{
100100
set
101101
{
102-
if(SetValue(ref selectedRepeatMode, value))
102+
if (SetValue(ref selectedRepeatMode, value))
103103
{
104104
reset = true;
105105
if (animationUpdater != null) animationUpdater.RepeatMode = value;
@@ -120,6 +120,7 @@ public AnimationRepeatMode SelectedRepeatMode
120120

121121
private const int NumSegments = 100;
122122
private const int Theta = 24;
123+
private long startAniTime = 0;
123124
private CancellationTokenSource cts = new CancellationTokenSource();
124125
private SynchronizationContext context = SynchronizationContext.Current;
125126

@@ -151,7 +152,7 @@ public MainViewModel()
151152
HitLineGeometry.Indices.Add(0);
152153
HitLineGeometry.Indices.Add(1);
153154
LoadFile();
154-
compositeHelper.Rendering += CompositeHelper_Rendering;
155+
compositeHelper.Rendering += CompositeHelper_Rendering;
155156
}
156157

157158
private void LoadFile()
@@ -163,16 +164,16 @@ private void LoadFile()
163164
scene = importer.Load("Solus_The_Knight.fbx");
164165
ModelGroup.AddNode(scene.Root);
165166
Animations = scene.Animations.Select(x => x.Name).ToArray();
166-
foreach(var node in scene.Root.Items.Traverse(false))
167+
foreach (var node in scene.Root.Items.Traverse(false))
167168
{
168-
if(node is BoneSkinMeshNode m)
169-
{
169+
if (node is BoneSkinMeshNode m)
170+
{
170171
if (!m.IsSkeletonNode)
171172
{
172173
m.IsThrowingShadow = true;
173174
m.WireframeColor = new SharpDX.Color4(0, 0, 1, 1);
174175
boneSkinNodes.Add(m);
175-
m.MouseDown += M_MouseDown;
176+
m.MouseDown += HandleMouseDown;
176177
}
177178
else
178179
{
@@ -183,7 +184,7 @@ private void LoadFile()
183184
}
184185
}
185186

186-
private void M_MouseDown(object sender, SceneNodeMouseDownArgs e)
187+
private void HandleMouseDown(object sender, SceneNodeMouseDownArgs e)
187188
{
188189
var result = e.HitResult;
189190
HitLineGeometry.Positions[0] = result.PointHit - result.NormalAtHit * 0.5f;
@@ -200,10 +201,16 @@ private void CompositeHelper_Rendering(object sender, System.Windows.Media.Rende
200201
animationUpdater.Reset();
201202
animationUpdater.RepeatMode = SelectedRepeatMode;
202203
reset = false;
204+
startAniTime = 0;
203205
}
204206
else
205207
{
206-
animationUpdater.Update(Stopwatch.GetTimestamp(), Stopwatch.Frequency);
208+
if (startAniTime == 0)
209+
{
210+
startAniTime = Stopwatch.GetTimestamp();
211+
}
212+
var elapsed = Stopwatch.GetTimestamp() - startAniTime;
213+
animationUpdater.Update(elapsed, Stopwatch.Frequency);
207214
}
208215
}
209216
}

Source/Examples/WPF.SharpDX/FileLoadDemo/MainViewModel.cs

+58-27
Original file line numberDiff line numberDiff line change
@@ -106,24 +106,40 @@ public bool IsLoading
106106
get => isLoading;
107107
}
108108

109-
private bool enableAnimation = false;
110-
public bool EnableAnimation
109+
private bool isPlaying = false;
110+
public bool IsPlaying
111+
{
112+
private set => SetValue(ref isPlaying, value);
113+
get => isPlaying;
114+
}
115+
116+
private float startTime;
117+
public float StartTime
118+
{
119+
private set => SetValue(ref startTime, value);
120+
get => startTime;
121+
}
122+
123+
private float endTime;
124+
public float EndTime
125+
{
126+
private set => SetValue(ref endTime, value);
127+
get => endTime;
128+
}
129+
130+
private float currAnimationTime = 0;
131+
public float CurrAnimationTime
111132
{
112133
set
113134
{
114-
if (SetValue(ref enableAnimation, value))
135+
if (EndTime == 0)
136+
{ return; }
137+
if (SetValue(ref currAnimationTime, value % EndTime + StartTime))
115138
{
116-
if (value)
117-
{
118-
StartAnimation();
119-
}
120-
else
121-
{
122-
StopAnimation();
123-
}
139+
animationUpdater?.Update(value, 1);
124140
}
125141
}
126-
get { return enableAnimation; }
142+
get => currAnimationTime;
127143
}
128144

129145
public ObservableCollection<IAnimationUpdater> Animations { get; } = new ObservableCollection<IAnimationUpdater>();
@@ -138,20 +154,19 @@ public IAnimationUpdater SelectedAnimation
138154
if (SetValue(ref selectedAnimation, value))
139155
{
140156
StopAnimation();
157+
CurrAnimationTime = 0;
141158
if (value != null)
142159
{
143160
animationUpdater = value;
144161
animationUpdater.Reset();
145162
animationUpdater.RepeatMode = AnimationRepeatMode.Loop;
146-
animationUpdater.Speed = Speed;
163+
StartTime = value.StartTime;
164+
EndTime = value.EndTime;
147165
}
148166
else
149167
{
150168
animationUpdater = null;
151-
}
152-
if (enableAnimation)
153-
{
154-
StartAnimation();
169+
StartTime = EndTime = 0;
155170
}
156171
}
157172
}
@@ -166,18 +181,15 @@ public float Speed
166181
{
167182
set
168183
{
169-
if (SetValue(ref speed, value))
170-
{
171-
if (animationUpdater != null)
172-
animationUpdater.Speed = value;
173-
}
184+
SetValue(ref speed, value);
174185
}
175186
get => speed;
176187
}
188+
177189
private Point3D modelCentroid = default;
178190
public Point3D ModelCentroid
179191
{
180-
private set =>SetValue(ref modelCentroid, value);
192+
private set => SetValue(ref modelCentroid, value);
181193
get => modelCentroid;
182194
}
183195
private BoundingBox modelBound = new BoundingBox();
@@ -188,13 +200,16 @@ public BoundingBox ModelBound
188200
}
189201
public TextureModel EnvironmentMap { get; }
190202

203+
public ICommand PlayCommand { get; }
204+
191205
private SynchronizationContext context = SynchronizationContext.Current;
192206
private HelixToolkitScene scene;
193207
private IAnimationUpdater animationUpdater;
194208
private List<BoneSkinMeshNode> boneSkinNodes = new List<BoneSkinMeshNode>();
195209
private List<BoneSkinMeshNode> skeletonNodes = new List<BoneSkinMeshNode>();
196210
private CompositionTargetEx compositeHelper = new CompositionTargetEx();
197-
211+
private long initTimeStamp = 0;
212+
198213
private MainWindow mainWindow = null;
199214

200215
public MainViewModel(MainWindow window)
@@ -223,6 +238,18 @@ public MainViewModel(MainWindow window)
223238
CopyAsHiresBitmapCommand = new DelegateCommand(() => { CopyAsHiResBitmapToClipBoard(mainWindow.view); });
224239

225240
EnvironmentMap = TextureModel.Create("Cubemap_Grandcanyon.dds");
241+
242+
PlayCommand = new DelegateCommand(() =>
243+
{
244+
if (!IsPlaying && SelectedAnimation != null)
245+
{
246+
StartAnimation();
247+
}
248+
else
249+
{
250+
StopAnimation();
251+
}
252+
});
226253
}
227254

228255
private void CopyAsBitmapToClipBoard(Viewport3DX viewport)
@@ -299,8 +326,8 @@ private void OpenFile()
299326
var oldNode = GroupModel.SceneNode.Items.ToArray();
300327
GroupModel.Clear(false);
301328
Task.Run(() =>
302-
{
303-
foreach (var node in oldNode)
329+
{
330+
foreach (var node in oldNode)
304331
{ node.Dispose(); }
305332
});
306333
if (scene != null)
@@ -348,19 +375,23 @@ private void OpenFile()
348375

349376
public void StartAnimation()
350377
{
378+
initTimeStamp = Stopwatch.GetTimestamp();
351379
compositeHelper.Rendering += CompositeHelper_Rendering;
380+
IsPlaying = true;
352381
}
353382

354383
public void StopAnimation()
355384
{
385+
IsPlaying = false;
356386
compositeHelper.Rendering -= CompositeHelper_Rendering;
357387
}
358388

359389
private void CompositeHelper_Rendering(object sender, System.Windows.Media.RenderingEventArgs e)
360390
{
361391
if (animationUpdater != null)
362392
{
363-
animationUpdater.Update(Stopwatch.GetTimestamp(), Stopwatch.Frequency);
393+
var elapsed = (Stopwatch.GetTimestamp() - initTimeStamp) * speed;
394+
CurrAnimationTime = elapsed / Stopwatch.Frequency;
364395
}
365396
}
366397

Source/Examples/WPF.SharpDX/FileLoadDemo/MainWindow.xaml

+24-14
Original file line numberDiff line numberDiff line change
@@ -98,27 +98,37 @@
9898
<CheckBox Margin="4" IsChecked="{Binding RenderEnvironmentMap}">Render EnvironmentMap</CheckBox>
9999
<CheckBox Margin="4" IsChecked="{Binding RenderFlat}">Flat Shading</CheckBox>
100100
<Separator />
101-
<CheckBox
102-
x:Name="aniCheckbox"
103-
Margin="4"
104-
IsChecked="{Binding EnableAnimation}">
105-
Enable Animation
106-
</CheckBox>
107101
<TextBlock>Animations</TextBlock>
108102
<ComboBox
109103
DisplayMemberPath="Name"
110104
IsEnabled="{Binding ElementName=aniCheckbox, Path=IsChecked}"
111105
ItemsSource="{Binding Animations}"
112106
SelectedItem="{Binding SelectedAnimation}" />
113-
<StackPanel Orientation="Horizontal">
114-
<Label Content="PlayBack Speed: " />
115-
<TextBlock VerticalAlignment="Center" Text="{Binding Speed}" />
107+
<StackPanel Orientation="Vertical">
108+
<StackPanel Orientation="Horizontal">
109+
<Label Content="PlayBack Speed: " />
110+
<TextBlock VerticalAlignment="Center" Text="{Binding Speed}" />
111+
<Button Command="{Binding PlayCommand}">Play</Button>
112+
</StackPanel>
113+
114+
<Slider
115+
Maximum="50"
116+
Minimum="0"
117+
SmallChange="0.5"
118+
Value="{Binding Speed}" />
119+
<Label>
120+
<TextBlock>
121+
<Run>TimeStamp:</Run>
122+
<Run Text="{Binding CurrAnimationTime}" />
123+
</TextBlock>
124+
</Label>
125+
<Slider
126+
LargeChange="1"
127+
Maximum="{Binding EndTime}"
128+
Minimum="{Binding StartTime}"
129+
SmallChange="0.1"
130+
Value="{Binding CurrAnimationTime}" />
116131
</StackPanel>
117-
<Slider
118-
Maximum="50"
119-
Minimum="0"
120-
SmallChange="0.5"
121-
Value="{Binding Speed}" />
122132
<Separator />
123133
<TextBlock>Scene Graph</TextBlock>
124134
</StackPanel>

0 commit comments

Comments
 (0)