| 1 |
using System; |
| 2 |
using System.Collections.Generic; |
| 3 |
using Oni.Xml; |
| 4 |
using Oni.Metadata; |
| 5 |
|
| 6 |
namespace 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 |
} |