Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
AnimationBlender.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
6 using SiliconStudio.Core;
7 using SiliconStudio.Core.Collections;
8 using SiliconStudio.Core.Mathematics;
9 using SiliconStudio.Paradox.Effects;
10 using SiliconStudio.Paradox.Engine;
11 
12 namespace SiliconStudio.Paradox.DataModel
13 {
14  /// <summary>
15  /// Performs animation blending.
16  /// For now, all AnimationClip must target the same skeleton.
17  /// </summary>
18  public sealed class AnimationBlender
19  {
20  private static Stack<AnimationClipEvaluator> evaluatorPool = new Stack<AnimationClipEvaluator>();
21 
22  // Pool of available objects for intermediate results.
23  private static Stack<AnimationClipResult> availableResultsPool = new Stack<AnimationClipResult>();
24 
25  private Stack<AnimationClipResult> animationStack = new Stack<AnimationClipResult>();
26 
27  private HashSet<AnimationClipEvaluator> evaluators = new HashSet<AnimationClipEvaluator>();
28  private HashSet<AnimationClip> clips = new HashSet<AnimationClip>();
29  private Dictionary<string, Channel> channelsByName = new Dictionary<string, Channel>();
30  private List<Channel> channels = new List<Channel>();
31  private int structureSize;
32 
34  {
35  // Check if this clip has already been used
36  if (clips.Add(clip))
37  {
38  // If new clip, let's scan its channel to add new ones.
39  foreach (var curve in clip.Channels)
40  {
41  Channel channel;
42  if (channelsByName.TryGetValue(curve.Key, out channel))
43  {
44  // TODO: Check if channel matches
45  }
46  else
47  {
48  // New channel, add it to every evaluator
49 
50  // Find blend type
51  var blendType = BlendType.None;
52  var elementType = curve.Value.ElementType;
53 
54  if (elementType == typeof(Quaternion))
55  {
56  blendType = BlendType.Quaternion;
57  }
58  else if (elementType == typeof(float))
59  {
60  blendType = BlendType.Float1;
61  }
62  else if (elementType == typeof(Vector2))
63  {
64  blendType = BlendType.Float2;
65  }
66  else if (elementType == typeof(Vector3))
67  {
68  blendType = BlendType.Float3;
69  }
70  else if (elementType == typeof(Vector4))
71  {
72  blendType = BlendType.Float4;
73  }
74 
75  // Create channel structure
76  channel.BlendType = blendType;
77  channel.Offset = structureSize;
78  channel.PropertyName = curve.Key;
79 
80  // TODO: Remove this totally hardcoded property name parsing!
81  channel.NodeName = curve.Value.NodeName;
82  channel.Type = curve.Value.Type;
83 
84  channel.Size = curve.Value.ElementSize;
85 
86  // Add channel
87  channelsByName.Add(channel.PropertyName, channel);
88  channels.Add(channel);
89 
90  // Update new structure size
91  // We also reserve space for a float that will specify channel existence and factor in case of subtree blending
92  structureSize += sizeof(float) + channel.Size;
93 
94  // Add new channel update info to every evaluator
95  // TODO: Maybe it's better lazily done? (avoid need to store list of all evaluators)
96  foreach (var currentEvaluator in evaluators)
97  {
98  currentEvaluator.AddChannel(ref channel);
99  }
100  }
101  }
102  }
103 
104  // Update results to fit the new data size
105  lock (availableResultsPool)
106  {
107  foreach (var result in availableResultsPool)
108  {
109  if (result.DataSize < structureSize)
110  {
111  result.DataSize = structureSize;
112  result.Data = new byte[structureSize];
113  }
114  }
115  }
116 
117  // Create evaluator and store it in list of instantiated evaluators
118  AnimationClipEvaluator evaluator;
119  lock (evaluatorPool)
120  {
121  if (evaluatorPool.Count > 0)
122  {
123  evaluator = evaluatorPool.Pop();
124  }
125  else
126  {
127  evaluator = new AnimationClipEvaluator();
128  }
129  }
130 
131  evaluator.Initialize(clip, channels);
132  evaluators.Add(evaluator);
133 
134  return evaluator;
135  }
136 
138  {
139  lock (evaluatorPool)
140  {
141  evaluators.Remove(evaluator);
142  evaluator.Cleanup();
143  evaluatorPool.Push(evaluator);
144  }
145  }
146 
147  public static unsafe void Blend(AnimationBlendOperation blendOperation, float blendFactor, AnimationClipResult sourceLeft, AnimationClipResult sourceRight, AnimationClipResult result)
148  {
149  fixed (byte* sourceLeftDataStart = sourceLeft.Data)
150  fixed (byte* sourceRightDataStart = sourceRight.Data)
151  fixed (byte* resultDataStart = result.Data)
152  {
153  foreach (var channel in sourceLeft.Channels)
154  {
155  int offset = channel.Offset;
156  var sourceLeftData = (float*)(sourceLeftDataStart + offset);
157  var sourceRightData = (float*)(sourceRightDataStart + offset);
158  var resultData = (float*)(resultDataStart + offset);
159 
160  float factorLeft = *sourceLeftData++;
161  float factorRight = *sourceRightData++;
162 
163  // Ignore this channel
164  if (factorLeft == 0.0f && factorRight == 0.0f)
165  {
166  *resultData++ = 0.0f;
167  continue;
168  }
169 
170  // Use left value
171  if (factorLeft > 0.0f && factorRight == 0.0f)
172  {
173  *resultData++ = 1.0f;
174  Utilities.CopyMemory((IntPtr)resultData, (IntPtr)sourceLeftData, channel.Size);
175  continue;
176  }
177 
178  // Use right value
179  if (factorRight > 0.0f && factorLeft == 0.0f)
180  {
181  *resultData++ = 1.0f;
182  Utilities.CopyMemory((IntPtr)resultData, (IntPtr)sourceRightData, channel.Size);
183  continue;
184  }
185 
186  // Blending
187  *resultData++ = 1.0f;
188 
189  switch (blendOperation)
190  {
191  case AnimationBlendOperation.LinearBlend:
192  // Perform linear blending
193  // It will blend between left (0.0) and right (1.0)
194  switch (channel.BlendType)
195  {
196  case BlendType.None:
197  Utilities.CopyMemory((IntPtr)resultData, (IntPtr)sourceLeftData, channel.Size);
198  break;
199  case BlendType.Float2:
200  Vector2.Lerp(ref *(Vector2*)sourceLeftData, ref *(Vector2*)sourceRightData, blendFactor, out *(Vector2*)resultData);
201  break;
202  case BlendType.Float3:
203  Vector3.Lerp(ref *(Vector3*)sourceLeftData, ref *(Vector3*)sourceRightData, blendFactor, out *(Vector3*)resultData);
204  break;
205  case BlendType.Quaternion:
206  Quaternion.Slerp(ref *(Quaternion*)sourceLeftData, ref *(Quaternion*)sourceRightData, blendFactor, out *(Quaternion*)resultData);
207  break;
208  default:
209  throw new ArgumentOutOfRangeException();
210  }
211  break;
212  case AnimationBlendOperation.Add:
213  // Perform additive blending
214  // It will blend between left (0.0) and left + right (1.0).
215  switch (channel.BlendType)
216  {
217  case BlendType.None:
218  Utilities.CopyMemory((IntPtr)resultData, (IntPtr)sourceLeftData, channel.Size);
219  break;
220  case BlendType.Float2:
221  Vector2 rightValue2;
222  Vector2.Add(ref *(Vector2*)sourceLeftData, ref *(Vector2*)sourceRightData, out rightValue2);
223  Vector2.Lerp(ref *(Vector2*)sourceLeftData, ref rightValue2, blendFactor, out *(Vector2*)resultData);
224  break;
225  case BlendType.Float3:
226  Vector3 rightValue3;
227  Vector3.Add(ref *(Vector3*)sourceLeftData, ref *(Vector3*)sourceRightData, out rightValue3);
228  Vector3.Lerp(ref *(Vector3*)sourceLeftData, ref rightValue3, blendFactor, out *(Vector3*)resultData);
229  break;
230  case BlendType.Quaternion:
231  Quaternion rightValueQ;
232  Quaternion.Multiply(ref *(Quaternion*)sourceLeftData, ref *(Quaternion*)sourceRightData, out rightValueQ);
233  Quaternion.Normalize(ref rightValueQ, out rightValueQ);
234  Quaternion.Slerp(ref *(Quaternion*)sourceLeftData, ref rightValueQ, blendFactor, out *(Quaternion*)resultData);
235  break;
236  default:
237  throw new ArgumentOutOfRangeException();
238  }
239  break;
240  case AnimationBlendOperation.Subtract:
241  // Perform subtractive blending
242  // It will blend between left (0.0) and left - right (1.0).
243  switch (channel.BlendType)
244  {
245  case BlendType.None:
246  Utilities.CopyMemory((IntPtr)resultData, (IntPtr)sourceLeftData, channel.Size);
247  break;
248  case BlendType.Float2:
249  Vector2 rightValue2;
250  Vector2.Subtract(ref *(Vector2*)sourceLeftData, ref *(Vector2*)sourceRightData, out rightValue2);
251  Vector2.Lerp(ref *(Vector2*)sourceLeftData, ref rightValue2, blendFactor, out *(Vector2*)resultData);
252  break;
253  case BlendType.Float3:
254  Vector3 rightValue3;
255  Vector3.Subtract(ref *(Vector3*)sourceLeftData, ref *(Vector3*)sourceRightData, out rightValue3);
256  Vector3.Lerp(ref *(Vector3*)sourceLeftData, ref rightValue3, blendFactor, out *(Vector3*)resultData);
257  break;
258  case BlendType.Quaternion:
259  Quaternion rightValueQ;
260  // blend between left (0.0) and left * conjugate(right) (1.0)
261  Quaternion.Invert(ref *(Quaternion*)sourceRightData, out rightValueQ);
262  Quaternion.Multiply(ref rightValueQ, ref *(Quaternion*)sourceLeftData, out rightValueQ);
263  Quaternion.Normalize(ref rightValueQ, out rightValueQ);
264  //throw new NotImplementedException();
265  Quaternion.Slerp(ref *(Quaternion*)sourceLeftData, ref rightValueQ, blendFactor, out *(Quaternion*)resultData);
266  break;
267  default:
268  throw new ArgumentOutOfRangeException();
269  }
270  break;
271  default:
272  throw new ArgumentOutOfRangeException("blendOperation");
273  }
274  }
275  }
276  }
277 
278  /// <summary>
279  /// Computes the specified animation operations.
280  /// </summary>
281  /// <param name="animationOperations">The animation operations to perform.</param>
282  /// <param name="result">The optional result (if not null, it expects the final stack to end up with this element).</param>
283  public void Compute(FastList<AnimationOperation> animationOperations, ref AnimationClipResult result)
284  {
285  // Clear animation stack
286  animationStack.Clear();
287 
288  // Apply first operation (should be a push), directly into result (considered first item in the stack)
289  var animationOperation0 = animationOperations.Items[0];
290 
291  if (animationOperation0.Type != AnimationOperationType.Push)
292  throw new InvalidOperationException("First operation should be a push");
293 
294  // TODO: Either result is null (should have a Pop operation) or result is non null (stack end up being size 1)
295  var hasResult = result != null;
296  if (hasResult)
297  {
298  // Ensure there is enough size
299  // TODO: Force result allocation to happen on user side?
300  if (result.DataSize < structureSize)
301  {
302  result.DataSize = structureSize;
303  result.Data = new byte[structureSize];
304  }
305 
306  result.Channels = channels;
307  }
308  else
309  {
310  result = AllocateIntermediateResult();
311  }
312 
313  animationOperation0.Evaluator.Compute((CompressedTimeSpan)animationOperation0.Time, result);
314 
315  animationStack.Push(result);
316 
317  for (int index = 1; index < animationOperations.Count; index++)
318  {
319  var animationOperation = animationOperations.Items[index];
320 
321  ApplyAnimationOperation(ref animationOperation);
322  }
323 
324  if (hasResult && (animationStack.Count != 1 || animationStack.Pop() != result))
325  {
326  throw new InvalidOperationException("Stack should end up with result.");
327  }
328  }
329 
330  private void ApplyAnimationOperation(ref AnimationOperation animationOperation)
331  {
332  switch (animationOperation.Type)
333  {
334  case AnimationOperationType.Blend:
335  {
336  // Blend stack[last - 1] and stack[last] into stack[last - 1], then pop stack[last]
337  var op2 = animationStack.Pop();
338  var op1 = animationStack.Peek();
339  Blend(animationOperation.BlendOperation, animationOperation.BlendFactor, op1, op2, op1);
340  FreeIntermediateResult(op2);
341  }
342  break;
343  case AnimationOperationType.Push:
344  {
345  var op = AllocateIntermediateResult();
346  animationOperation.Evaluator.Compute((CompressedTimeSpan)animationOperation.Time, op);
347  animationStack.Push(op);
348  }
349  break;
350  case AnimationOperationType.Pop:
351  {
352  var op = animationStack.Pop();
353  animationOperation.Evaluator.AddCurveValues((CompressedTimeSpan)animationOperation.Time, op);
354  }
355  break;
356  }
357  }
358 
359  private AnimationClipResult AllocateIntermediateResult()
360  {
361  lock (availableResultsPool)
362  {
363  if (availableResultsPool.Count > 0)
364  {
365  var result = availableResultsPool.Pop();
366 
367  if (result.DataSize < structureSize)
368  {
369  result.DataSize = structureSize;
370  result.Data = new byte[structureSize];
371  }
372 
373  result.Channels = channels;
374 
375  return result;
376  }
377  }
378 
379  // Nothing available, create new one.
380  return new AnimationClipResult
381  {
382  DataSize = structureSize,
383  Data = new byte[structureSize],
384  Channels = channels,
385  };
386  }
387 
388  internal void FreeIntermediateResult(AnimationClipResult result)
389  {
390  // Returns it to pool of available intermediate results
391  lock (availableResultsPool)
392  {
393  result.Channels = null;
394  availableResultsPool.Push(result);
395  }
396  }
397 
398  [DataContract]
399  public enum BlendType
400  {
401  None,
402  Float1,
403  Float2,
404  Float3,
405  Float4,
406  Quaternion,
407  }
408 
409  [DataContract]
410  public struct Channel
411  {
412  public string PropertyName;
413  public string NodeName;
415  public int Offset;
416  public int Size;
418 
419  public override string ToString()
420  {
421  return PropertyName;
422  }
423  }
424  }
425 }
AnimationClipEvaluator CreateEvaluator(AnimationClip clip)
Dictionary< string, Channel > Channels
Gets the channels of this clip.
Represents a two dimensional mathematical vector.
Definition: Vector2.cs:42
static unsafe void Blend(AnimationBlendOperation blendOperation, float blendFactor, AnimationClipResult sourceLeft, AnimationClipResult sourceRight, AnimationClipResult result)
void Compute(FastList< AnimationOperation > animationOperations, ref AnimationClipResult result)
Computes the specified animation operations.
AnimationBlendOperation
Describes the type of animation blend operation.
Represents a three dimensional mathematical vector.
Definition: Vector3.cs:42
A single animation operation (push or blend).
void ReleaseEvaluator(AnimationClipEvaluator evaluator)
An aggregation of AnimationCurve with their channel names.
List< AnimationBlender.Channel > Channels
Gets or sets the animation channel descriptions.
Represents a four dimensional mathematical vector.
Definition: Vector4.cs:42
Represents a four dimensional mathematical quaternion.
Definition: Quaternion.cs:45
SiliconStudio.Core.Mathematics.Quaternion Quaternion
Performs animation blending. For now, all AnimationClip must target the same skeleton.
Blend
Blend option. A blend option identifies the data source and an optional pre-blend operation...
Definition: Blend.cs:14
Evaluates AnimationClip to a AnimationClipResult at a given time.