source: OniSplit/Totoro/AnimationDatWriter.cs@ 1114

Last change on this file since 1114 was 1114, checked in by iritscen, 5 years ago

Adding OniSplit source code (v0.9.99.0). Many thanks to Neo for all his work over the years.

File size: 20.8 KB
Line 
1using System;
2using System.Collections.Generic;
3using Oni.Xml;
4using Oni.Metadata;
5
6namespace Oni.Totoro
7{
8 internal class AnimationDatWriter
9 {
10 private Animation animation;
11 private List<DatExtent> extents;
12 private DatExtentInfo extentInfo;
13 private Importer importer;
14 private BinaryWriter dat;
15 private BinaryWriter raw;
16
17 #region private class DatExtent
18
19 private class DatExtent
20 {
21 public readonly int Frame;
22 public readonly AttackExtent Extent;
23
24 public DatExtent(int frame, AttackExtent extent)
25 {
26 this.Frame = frame;
27 this.Extent = extent;
28 }
29 }
30
31 #endregion
32 #region private class DatExtentInfo
33
34 private class DatExtentInfo
35 {
36 public float MaxDistance;
37 public float MinY = 1e09f;
38 public float MaxY = -1e09f;
39 public readonly DatExtentInfoFrame FirstExtent = new DatExtentInfoFrame();
40 public readonly DatExtentInfoFrame MaxExtent = new DatExtentInfoFrame();
41 }
42
43 #endregion
44 #region private class DatExtentInfoFrame
45
46 private class DatExtentInfoFrame
47 {
48 public int Frame = -1;
49 public int Attack;
50 public int AttackOffset;
51 public Vector2 Location;
52 public float Height;
53 public float Length;
54 public float MinY;
55 public float MaxY;
56 public float Angle;
57 }
58
59 #endregion
60
61 private AnimationDatWriter()
62 {
63 }
64
65 public static void Write(Animation animation, Importer importer, BinaryWriter dat)
66 {
67 var writer = new AnimationDatWriter
68 {
69 animation = animation,
70 importer = importer,
71 dat = dat,
72 raw = importer.RawWriter
73 };
74
75 writer.WriteAnimation();
76 }
77
78 private void WriteAnimation()
79 {
80 extentInfo = new DatExtentInfo();
81 extents = new List<DatExtent>();
82
83 if (animation.Attacks.Count > 0)
84 {
85 if (animation.Attacks[0].Extents.Count == 0)
86 GenerateExtentInfo();
87
88 foreach (var attack in animation.Attacks)
89 {
90 int frame = attack.Start;
91
92 foreach (var extent in attack.Extents)
93 extents.Add(new DatExtent(frame++, extent));
94 }
95
96 GenerateExtentSummary();
97 }
98
99 var rotations = animation.Rotations;
100 int frameSize = animation.FrameSize;
101
102 if (frameSize == 16 && (animation.Flags & AnimationFlags.Overlay) == 0)
103 {
104 rotations = CompressFrames(rotations);
105 frameSize = 6;
106 }
107
108 dat.Write(0);
109 WriteRawArray(animation.Heights, x => raw.Write(x));
110 WriteRawArray(animation.Velocities, x => raw.Write(x));
111 WriteRawArray(animation.Attacks, Write);
112 WriteRawArray(animation.SelfDamage, Write);
113 WriteRawArray(animation.MotionBlur, Write);
114 WriteRawArray(animation.Shortcuts, Write);
115 WriteThrowInfo();
116 WriteRawArray(animation.Footsteps, Write);
117 WriteRawArray(animation.Particles, Write);
118 WriteRawArray(animation.Positions, Write);
119 WriteRotations(rotations, frameSize);
120 WriteRawArray(animation.Sounds, Write);
121 dat.Write((int)animation.Flags);
122
123 if (!string.IsNullOrEmpty(animation.DirectAnimations[0]))
124 dat.Write(importer.CreateInstance(TemplateTag.TRAM, animation.DirectAnimations[0]));
125 else
126 dat.Write(0);
127
128 if (!string.IsNullOrEmpty(animation.DirectAnimations[1]))
129 dat.Write(importer.CreateInstance(TemplateTag.TRAM, animation.DirectAnimations[1]));
130 else
131 dat.Write(0);
132
133 dat.Write((int)animation.OverlayUsedBones);
134 dat.Write((int)animation.OverlayReplacedBones);
135 dat.Write(animation.FinalRotation);
136 dat.Write((ushort)animation.Direction);
137 dat.WriteUInt16(animation.Vocalization);
138 WriteExtentInfo();
139 dat.Write(animation.Impact, 16);
140 dat.WriteUInt16(animation.HardPause);
141 dat.WriteUInt16(animation.SoftPause);
142 dat.Write(animation.Sounds.Count);
143 dat.Skip(6);
144 dat.WriteUInt16(60);
145 dat.WriteUInt16(frameSize);
146 dat.WriteUInt16((ushort)animation.Type);
147 dat.WriteUInt16((ushort)animation.AimingType);
148 dat.WriteUInt16((ushort)animation.FromState);
149 dat.WriteUInt16((ushort)animation.ToState);
150 dat.WriteUInt16(rotations.Count);
151 dat.WriteUInt16(animation.Velocities.Count);
152 dat.WriteUInt16(animation.Velocities.Count);
153 dat.WriteUInt16((ushort)animation.Varient);
154 dat.Skip(2);
155 dat.WriteUInt16(animation.AtomicStart);
156 dat.WriteUInt16(animation.AtomicEnd);
157 dat.WriteUInt16(animation.InterpolationEnd);
158 dat.WriteUInt16(animation.InterpolationMax);
159 dat.WriteUInt16(animation.ActionFrame);
160 dat.WriteUInt16(animation.FirstLevelAvailable);
161 dat.WriteByte(animation.InvulnerableStart);
162 dat.WriteByte(animation.InvulnerableEnd);
163 dat.WriteByte(animation.Attacks.Count);
164 dat.WriteByte(animation.SelfDamage.Count);
165 dat.WriteByte(animation.MotionBlur.Count);
166 dat.WriteByte(animation.Shortcuts.Count);
167 dat.WriteByte(animation.Footsteps.Count);
168 dat.WriteByte(animation.Particles.Count);
169 }
170
171 private void WriteRotations(List<List<KeyFrame>> rotations, int frameSize)
172 {
173 dat.Write(raw.Align32());
174
175 var offsets = new ushort[rotations.Count];
176
177 offsets[0] = (ushort)(rotations.Count * 2);
178
179 for (int i = 1; i < offsets.Length; i++)
180 offsets[i] = (ushort)(offsets[i - 1] + rotations[i - 1].Count * (frameSize + 1) - 1);
181
182 raw.Write(offsets);
183
184 foreach (var keys in rotations)
185 {
186 foreach (var key in keys)
187 {
188 switch (frameSize)
189 {
190 case 6:
191 raw.WriteInt16((short)(Math.Round(key.Rotation.X / 180.0f * 32767.5f)));
192 raw.WriteInt16((short)(Math.Round(key.Rotation.Y / 180.0f * 32767.5f)));
193 raw.WriteInt16((short)(Math.Round(key.Rotation.Z / 180.0f * 32767.5f)));
194 break;
195
196 case 16:
197 raw.Write(new Quaternion(key.Rotation));
198 break;
199 }
200
201 if (key != keys.Last())
202 raw.WriteByte(key.Duration);
203 }
204 }
205 }
206
207 private void WriteThrowInfo()
208 {
209 if (animation.ThrowSource == null)
210 {
211 dat.Write(0);
212 return;
213 }
214
215 dat.Write(raw.Align32());
216
217 raw.Write(animation.ThrowSource.Position);
218 raw.Write(animation.ThrowSource.Angle);
219 raw.Write(animation.ThrowSource.Distance);
220 raw.WriteUInt16((ushort)animation.ThrowSource.Type);
221 }
222
223 private void WriteExtentInfo()
224 {
225 dat.Write(extentInfo.MaxDistance);
226 dat.Write(extentInfo.MinY);
227 dat.Write(extentInfo.MaxY);
228 dat.Write(animation.AttackRing);
229 Write(extentInfo.FirstExtent);
230 Write(extentInfo.MaxExtent);
231 dat.Write(0);
232 dat.Write(extents.Count);
233 WriteRawArray(extents, Write);
234 }
235
236 private void Write(DatExtentInfoFrame info)
237 {
238 dat.WriteInt16(info.Frame);
239 dat.WriteByte(info.Attack);
240 dat.WriteByte(info.AttackOffset);
241 dat.Write(info.Location);
242 dat.Write(info.Height);
243 dat.Write(info.Length);
244 dat.Write(info.MinY);
245 dat.Write(info.MaxY);
246 dat.Write(info.Angle);
247 }
248
249 private void Write(Position position)
250 {
251 raw.Write((short)Math.Round(position.X * 100.0f));
252 raw.Write((short)Math.Round(position.Z * 100.0f));
253 raw.Write((ushort)Math.Round(position.Height * 100.0f));
254 raw.Write((short)Math.Round(position.YOffset * 100.0f));
255 }
256
257 private void Write(Damage damage)
258 {
259 raw.WriteUInt16(damage.Points);
260 raw.WriteUInt16(damage.Frame);
261 }
262
263 private void Write(Shortcut shortcut)
264 {
265 raw.WriteUInt16((ushort)shortcut.FromState);
266 raw.WriteUInt16(shortcut.Length);
267 raw.Write(shortcut.ReplaceAtomic ? 1 : 0);
268 }
269
270 private void Write(Footstep footstep)
271 {
272 raw.WriteUInt16(footstep.Frame);
273 raw.WriteUInt16((ushort)footstep.Type);
274 }
275
276 private void Write(Sound sound)
277 {
278 raw.Write(sound.Name, 32);
279 raw.WriteUInt16(sound.Start);
280 }
281
282 private void Write(Particle particle)
283 {
284 raw.WriteUInt16(particle.Start);
285 raw.WriteUInt16(particle.End);
286 raw.Write((int)particle.Bone);
287 raw.Write(particle.Name, 16);
288 }
289
290 private void Write(MotionBlur m)
291 {
292 raw.Write((int)m.Bones);
293 raw.WriteUInt16(m.Start);
294 raw.WriteUInt16(m.End);
295 raw.WriteByte(m.Lifetime);
296 raw.WriteByte(m.Alpha);
297 raw.WriteByte(m.Interval);
298 raw.WriteByte(0);
299 }
300
301 private void Write(DatExtent extent)
302 {
303 raw.WriteInt16(extent.Frame);
304 raw.Write((short)Math.Round(extent.Extent.Angle * 65535.0f / 360.0f));
305 raw.Write((ushort)Math.Round(extent.Extent.Length * 100.0f));
306 raw.WriteInt16(0);
307 raw.Write((short)Math.Round(extent.Extent.MinY * 100.0f));
308 raw.Write((short)Math.Round(extent.Extent.MaxY * 100.0f));
309 }
310
311 private void Write(Attack attack)
312 {
313 raw.Write((int)attack.Bones);
314 raw.Write(attack.Knockback);
315 raw.Write((int)attack.Flags);
316 raw.WriteInt16(attack.HitPoints);
317 raw.WriteInt16(attack.Start);
318 raw.WriteInt16(attack.End);
319 raw.WriteInt16((short)attack.HitType);
320 raw.WriteInt16(attack.HitLength);
321 raw.WriteInt16(attack.StunLength);
322 raw.WriteInt16(attack.StaggerLength);
323 raw.WriteInt16(0);
324 raw.Write(0);
325 }
326
327 private void WriteRawArray<T>(List<T> list, Action<T> writeElement)
328 {
329 if (list.Count == 0)
330 {
331 dat.Write(0);
332 return;
333 }
334
335 dat.Write(raw.Align32());
336
337 foreach (T t in list)
338 writeElement(t);
339 }
340
341 private void GenerateExtentInfo()
342 {
343 float[] attackRing = animation.AttackRing;
344
345 Array.Clear(attackRing, 0, attackRing.Length);
346
347 foreach (var attack in animation.Attacks)
348 {
349 attack.Extents.Clear();
350
351 for (int frame = attack.Start; frame <= attack.End; frame++)
352 {
353 var position = animation.Positions[frame].XZ;
354 var framePoints = animation.AllPoints[frame];
355
356 for (int j = 0; j < framePoints.Count / 8; j++)
357 {
358 if ((attack.Bones & (BoneMask)(1 << j)) == 0)
359 continue;
360
361 for (int k = j * 8; k < (j + 1) * 8; k++)
362 {
363 var point = framePoints[k];
364 var delta = point.XZ - animation.Positions[0].XZ;
365
366 float distance = delta.Length();
367 float angle = FMath.Atan2(delta.X, delta.Y);
368
369 if (angle < 0.0f)
370 angle += MathHelper.TwoPi;
371
372 for (int r = 0; r < attackRing.Length; r++)
373 {
374 float ringAngle = r * MathHelper.TwoPi / attackRing.Length;
375
376 if (Math.Abs(ringAngle - angle) < MathHelper.ToRadians(30.0f))
377 attackRing[r] = Math.Max(attackRing[r], distance);
378 }
379 }
380 }
381
382 float minHeight = +1e09f;
383 float maxHeight = -1e09f;
384 float maxDistance = -1e09f;
385 float maxAngle = 0.0f;
386
387 for (int j = 0; j < framePoints.Count / 8; j++)
388 {
389 if ((attack.Bones & (BoneMask)(1 << j)) == 0)
390 continue;
391
392 for (int k = j * 8; k < (j + 1) * 8; k++)
393 {
394 var point = framePoints[k];
395 var delta = point.XZ - position;
396
397 float distance;
398
399 switch (animation.Direction)
400 {
401 case Direction.Forward:
402 distance = delta.Y;
403 break;
404 case Direction.Left:
405 distance = delta.X;
406 break;
407 case Direction.Right:
408 distance = -delta.X;
409 break;
410 case Direction.Backward:
411 distance = -delta.Y;
412 break;
413 default:
414 distance = delta.Length();
415 break;
416 }
417
418 if (distance > maxDistance)
419 {
420 maxDistance = distance;
421 maxAngle = FMath.Atan2(delta.X, delta.Y);
422 }
423
424 minHeight = Math.Min(minHeight, point.Y);
425 maxHeight = Math.Max(maxHeight, point.Y);
426 }
427 }
428
429 maxDistance = Math.Max(maxDistance, 0.0f);
430
431 if (maxAngle < 0)
432 maxAngle += MathHelper.TwoPi;
433
434 attack.Extents.Add(new AttackExtent
435 {
436 Angle = MathHelper.ToDegrees(maxAngle),
437 Length = maxDistance,
438 MinY = minHeight,
439 MaxY = maxHeight
440 });
441 }
442 }
443 }
444
445 private void GenerateExtentSummary()
446 {
447 if (extents.Count == 0)
448 return;
449
450 var positions = animation.Positions;
451 var attacks = animation.Attacks;
452 var heights = animation.Heights;
453
454 float minY = float.MaxValue, maxY = float.MinValue;
455
456 foreach (var datExtent in extents)
457 {
458 minY = Math.Min(minY, datExtent.Extent.MinY);
459 maxY = Math.Max(maxY, datExtent.Extent.MaxY);
460 }
461
462 var firstExtent = extents[0];
463 var maxExtent = firstExtent;
464
465 foreach (var datExtent in extents)
466 {
467 if (datExtent.Extent.Length + positions[datExtent.Frame].Z > maxExtent.Extent.Length + positions[maxExtent.Frame].Z)
468 maxExtent = datExtent;
469 }
470
471 int maxAttackIndex = 0, maxAttackOffset = 0;
472
473 for (int i = 0; i < attacks.Count; i++)
474 {
475 var attack = attacks[i];
476
477 if (attack.Start <= maxExtent.Frame && maxExtent.Frame <= attack.End)
478 {
479 maxAttackIndex = i;
480 maxAttackOffset = maxExtent.Frame - attack.Start;
481 break;
482 }
483 }
484
485 extentInfo.MaxDistance = animation.AttackRing.Max();
486 extentInfo.MinY = minY;
487 extentInfo.MaxY = maxY;
488
489 extentInfo.FirstExtent.Frame = firstExtent.Frame;
490 extentInfo.FirstExtent.Attack = 0;
491 extentInfo.FirstExtent.AttackOffset = 0;
492 extentInfo.FirstExtent.Location.X = positions[firstExtent.Frame].X;
493 extentInfo.FirstExtent.Location.Y = -positions[firstExtent.Frame].Z;
494 extentInfo.FirstExtent.Height = heights[firstExtent.Frame];
495 extentInfo.FirstExtent.Angle = MathHelper.ToRadians(firstExtent.Extent.Angle);
496 extentInfo.FirstExtent.Length = firstExtent.Extent.Length;
497 extentInfo.FirstExtent.MinY = FMath.Round(firstExtent.Extent.MinY, 2);
498 extentInfo.FirstExtent.MaxY = firstExtent.Extent.MaxY;
499
500 if ((animation.Flags & AnimationFlags.ThrowTarget) == 0)
501 {
502 extentInfo.MaxExtent.Frame = maxExtent.Frame;
503 extentInfo.MaxExtent.Attack = maxAttackIndex;
504 extentInfo.MaxExtent.AttackOffset = maxAttackOffset;
505 extentInfo.MaxExtent.Location.X = positions[maxExtent.Frame].X;
506 extentInfo.MaxExtent.Location.Y = -positions[maxExtent.Frame].Z;
507 extentInfo.MaxExtent.Height = heights[maxExtent.Frame];
508 extentInfo.MaxExtent.Angle = MathHelper.ToRadians(maxExtent.Extent.Angle);
509 extentInfo.MaxExtent.Length = maxExtent.Extent.Length;
510 extentInfo.MaxExtent.MinY = maxExtent.Extent.MinY;
511 extentInfo.MaxExtent.MaxY = FMath.Round(maxExtent.Extent.MaxY, 2);
512 }
513 }
514
515 private List<List<KeyFrame>> CompressFrames(List<List<KeyFrame>> tracks)
516 {
517 float tolerance = 0.5f;
518 float cosTolerance = FMath.Cos(MathHelper.ToRadians(tolerance) * 0.5f);
519 var newTracks = new List<List<KeyFrame>>();
520
521 foreach (var keys in tracks)
522 {
523 var newFrames = new List<KeyFrame>(keys.Count);
524
525 for (int i = 0; i < keys.Count;)
526 {
527 var key = keys[i];
528
529 int duration = key.Duration;
530 var q0 = new Quaternion(key.Rotation);
531
532 if (duration == 1)
533 {
534 for (int j = i + 2; j < keys.Count; j++)
535 {
536 if (!IsLinearRange(keys, i, j, cosTolerance))
537 break;
538
539 duration = j - i;
540 }
541 }
542
543 var eulerXYZ = q0.ToEulerXYZ();
544
545 newFrames.Add(new KeyFrame
546 {
547 Duration = duration,
548 Rotation = {
549 X = eulerXYZ.X,
550 Y = eulerXYZ.Y,
551 Z = eulerXYZ.Z
552 }
553 });
554
555 i += duration;
556 }
557
558 newTracks.Add(newFrames);
559 }
560
561 return newTracks;
562 }
563
564 private static bool IsLinearRange(List<KeyFrame> frames, int first, int last, float tolerance)
565 {
566 var q0 = new Quaternion(frames[first].Rotation);
567 var q1 = new Quaternion(frames[last].Rotation);
568 float length = last - first;
569
570 for (int i = first + 1; i < last; ++i)
571 {
572 float t = (i - first) / length;
573
574 var linear = Quaternion.Lerp(q0, q1, t);
575 var real = new Quaternion(frames[i].Rotation);
576 var error = Quaternion.Conjugate(linear) * real;
577
578 if (Math.Abs(error.W) < tolerance)
579 return false;
580 }
581
582 return true;
583 }
584 }
585}
Note: See TracBrowser for help on using the repository browser.