예제 #1
0
        public WadAnimation GetSavedAnimation(AnimationNode animation)
        {
            var wadAnim     = animation.WadAnimation.Clone();
            var directxAnim = animation.DirectXAnimation;

            // I need only to convert DX keyframes to Wad2 keyframes
            wadAnim.KeyFrames.Clear();
            foreach (var directxKeyframe in directxAnim.KeyFrames)
            {
                var keyframe = new WadKeyFrame();

                // Create the new bounding box
                keyframe.BoundingBox = new TombLib.BoundingBox(directxKeyframe.BoundingBox.Minimum,
                                                               directxKeyframe.BoundingBox.Maximum);

                // For now we take the first translation as offset
                keyframe.Offset = new System.Numerics.Vector3(directxKeyframe.Translations[0].X,
                                                              directxKeyframe.Translations[0].Y,
                                                              directxKeyframe.Translations[0].Z);

                // Convert angles from radians to degrees and save them
                foreach (var rot in directxKeyframe.Rotations)
                {
                    var angle = new WadKeyFrameRotation();
                    angle.Rotations = new System.Numerics.Vector3(rot.X * 180.0f / (float)Math.PI,
                                                                  rot.Y * 180.0f / (float)Math.PI,
                                                                  rot.Z * 180.0f / (float)Math.PI);
                    keyframe.Angles.Add(angle);
                }

                wadAnim.KeyFrames.Add(keyframe);
            }

            return(wadAnim);
        }
예제 #2
0
        public static Animation FromWad2(List <WadBone> bones, WadAnimation wadAnim)
        {
            Animation animation = new Animation();

            animation.KeyFrames = new List <KeyFrame>();

            for (int f = 0; f < wadAnim.KeyFrames.Count; f++)
            {
                KeyFrame    frame    = new KeyFrame();
                WadKeyFrame wadFrame = wadAnim.KeyFrames[f];

                for (int k = 0; k < bones.Count; k++)
                {
                    frame.Rotations.Add(Vector3.Zero);
                    frame.Translations.Add(Vector3.Zero);
                    frame.Quaternions.Add(Quaternion.Identity);
                    frame.TranslationsMatrices.Add(Matrix4x4.Identity);
                }

                frame.Translations[0]         = new Vector3(wadFrame.Offset.X, wadFrame.Offset.Y, wadFrame.Offset.Z);
                frame.TranslationsMatrices[0] = Matrix4x4.CreateTranslation(wadFrame.Offset);

                for (int k = 1; k < frame.Translations.Count; k++)
                {
                    frame.Translations[k] = Vector3.Zero;
                    frame.Quaternions[k]  = Quaternion.Identity;
                }

                for (int n = 0; n < frame.Rotations.Count; n++)
                {
                    frame.Rotations[n]   = wadFrame.Angles[n].RotationVectorInRadians;
                    frame.Quaternions[n] = Quaternion.CreateFromYawPitchRoll(wadFrame.Angles[n].RotationVectorInRadians.Y,
                                                                             wadFrame.Angles[n].RotationVectorInRadians.X,
                                                                             wadFrame.Angles[n].RotationVectorInRadians.Z);
                }

                frame.BoundingBox = new BoundingBox(wadFrame.BoundingBox.Minimum, wadFrame.BoundingBox.Maximum);

                animation.KeyFrames.Add(frame);
            }

            return(animation);
        }
예제 #3
0
        public static WadAnimation ImportAnimationFromModel(WadToolClass tool, IWin32Window owner, int nodeCount, string fileName)
        {
            IOModel tmpModel = null;

            // Import the model
            try
            {
                var settings = new IOGeometrySettings()
                {
                    ProcessAnimations = true, ProcessGeometry = false
                };
                using (var form = new GeometryIOSettingsDialog(settings))
                {
                    form.AddPreset(IOSettingsPresets.AnimationSettingsPresets);
                    string resultingExtension = Path.GetExtension(fileName).ToLowerInvariant();

                    if (resultingExtension.Equals(".fbx"))
                    {
                        form.SelectPreset("3dsmax Filmbox (FBX)");
                    }
                    else if (resultingExtension.Equals(".dae"))
                    {
                        form.SelectPreset("3dsmax COLLADA");
                    }

                    if (form.ShowDialog(owner) != DialogResult.OK)
                    {
                        return(null);
                    }

                    var importer = BaseGeometryImporter.CreateForFile(fileName, settings, null);
                    tmpModel = importer.ImportFromFile(fileName);

                    // We don't support animation importing from custom-written mqo importer yet...
                    if (importer is MetasequoiaImporter)
                    {
                        tool.SendMessage("Metasequoia importer isn't currently supported.", PopupType.Error);
                        return(null);
                    }

                    // If no animations, return null
                    if (tmpModel.Animations.Count == 0)
                    {
                        tool.SendMessage("Selected file has no supported animations!", PopupType.Error);
                        return(null);
                    }
                }
            }
            catch (Exception ex)
            {
                tool.SendMessage("Unknown error while importing animation. \n" + ex?.Message, PopupType.Error);
                logger.Warn(ex, "'ImportAnimationFromModel' failed.");
                return(null);
            }

            IOAnimation animToImport;

            if (tmpModel.Animations.Count > 1)
            {
                using (var dialog = new AnimationImportDialog(tmpModel.Animations.Select(o => o.Name).ToList()))
                {
                    dialog.ShowDialog(owner);
                    if (dialog.DialogResult == DialogResult.Cancel)
                    {
                        return(null);
                    }
                    else
                    {
                        animToImport = tmpModel.Animations[dialog.AnimationToImport];
                    }
                }
            }
            else
            {
                animToImport = tmpModel.Animations[0];
            }


            // Integrity check, for cases when something totally went wrong with assimp
            if (animToImport == null)
            {
                tool.SendMessage("Animation importer encountered serious error. No animation imported.", PopupType.Error);
                return(null);
            }

            // Integrity check, is there any valid frames?
            if (animToImport.Frames.Count <= 0)
            {
                tool.SendMessage("Selected animation has no frames!", PopupType.Error);
                return(null);
            }

            // Integrity check, number of bones = number of nodes?
            if (animToImport.NumNodes != nodeCount)
            {
                tool.SendMessage("Selected animation has different number of bones!", PopupType.Error);
                return(null);
            }

            WadAnimation animation = new WadAnimation();

            animation.Name = animToImport.Name;

            foreach (var frame in animToImport.Frames)
            {
                var keyFrame = new WadKeyFrame();
                keyFrame.Offset = frame.Offset;
                frame.Angles.ForEach(angle => keyFrame.Angles.Add(new WadKeyFrameRotation()
                {
                    Rotations = angle
                }));

                animation.KeyFrames.Add(keyFrame);
            }

            animation.EndFrame = (ushort)(animToImport.Frames.Count - 1);

            return(animation);
        }
예제 #4
0
        public void ConvertWad2DataToTr4()
        {
            ReportProgress(0, "Preparing WAD data");

            SortedList <WadMoveableId, WadMoveable> moveables = _level.Settings.WadGetAllMoveables();
            SortedList <WadStaticId, WadStatic>     statics   = _level.Settings.WadGetAllStatics();

            // First thing build frames
            ReportProgress(1, "Building animations");
            var animationDictionary = new Dictionary <WadAnimation, AnimationTr4HelperData>(new ReferenceEqualityComparer <WadAnimation>());

            foreach (WadMoveable moveable in moveables.Values)
            {
                foreach (var animation in moveable.Animations)
                {
                    AnimationTr4HelperData animationHelper = animationDictionary.TryAdd(animation, new AnimationTr4HelperData());
                    animationHelper.KeyFrameOffset = _frames.Count * 2;

                    // Store the frames in an intermediate data structure to pad them to the same size in the next step.
                    var unpaddedFrames = new List <short> [animation.KeyFrames.Count];
                    for (int i = 0; i < animation.KeyFrames.Count; ++i)
                    {
                        var         unpaddedFrame = new List <short>();
                        WadKeyFrame wadFrame      = animation.KeyFrames[i];
                        unpaddedFrames[i] = unpaddedFrame;

                        unpaddedFrame.Add((short)Math.Max(short.MinValue, Math.Min(short.MaxValue, wadFrame.BoundingBox.Minimum.X)));
                        unpaddedFrame.Add((short)Math.Max(short.MinValue, Math.Min(short.MaxValue, wadFrame.BoundingBox.Maximum.X)));
                        unpaddedFrame.Add((short)Math.Max(short.MinValue, Math.Min(short.MaxValue, -wadFrame.BoundingBox.Minimum.Y)));
                        unpaddedFrame.Add((short)Math.Max(short.MinValue, Math.Min(short.MaxValue, -wadFrame.BoundingBox.Maximum.Y)));
                        unpaddedFrame.Add((short)Math.Max(short.MinValue, Math.Min(short.MaxValue, wadFrame.BoundingBox.Minimum.Z)));
                        unpaddedFrame.Add((short)Math.Max(short.MinValue, Math.Min(short.MaxValue, wadFrame.BoundingBox.Maximum.Z)));

                        unpaddedFrame.Add((short)Math.Round(Math.Max(short.MinValue, Math.Min(short.MaxValue, wadFrame.Offset.X))));
                        unpaddedFrame.Add((short)Math.Round(Math.Max(short.MinValue, Math.Min(short.MaxValue, -wadFrame.Offset.Y))));
                        unpaddedFrame.Add((short)Math.Round(Math.Max(short.MinValue, Math.Min(short.MaxValue, wadFrame.Offset.Z))));

                        foreach (var angle in wadFrame.Angles)
                        {
                            WadKeyFrameRotation.ToTrAngle(angle, unpaddedFrame,
                                                          false,
                                                          _level.Settings.GameVersion == TRVersion.Game.TR4 ||
                                                          _level.Settings.GameVersion == TRVersion.Game.TRNG ||
                                                          _level.Settings.GameVersion == TRVersion.Game.TR5 ||
                                                          _level.Settings.GameVersion == TRVersion.Game.TR5Main);
                        }
                    }

                    // Figure out padding of the frames
                    int longestFrame = 0;
                    foreach (List <short> unpaddedFrame in unpaddedFrames)
                    {
                        longestFrame = Math.Max(longestFrame, unpaddedFrame.Count);
                    }

                    // Add frames
                    foreach (List <short> unpaddedFrame in unpaddedFrames)
                    {
                        _frames.AddRange(unpaddedFrame);
                        _frames.AddRange(Enumerable.Repeat((short)0, longestFrame - unpaddedFrame.Count));
                    }
                    animationHelper.KeyFrameSize = longestFrame;
                }
            }

            int lastAnimation    = 0;
            int lastAnimDispatch = 0;

            foreach (WadMoveable oldMoveable in moveables.Values)
            {
                var newMoveable = new tr_moveable();
                newMoveable.Animation   = checked ((ushort)(oldMoveable.Animations.Count != 0 ? lastAnimation : 0xffff));
                newMoveable.NumMeshes   = checked ((ushort)oldMoveable.Meshes.Count());
                newMoveable.ObjectID    = oldMoveable.Id.TypeId;
                newMoveable.FrameOffset = 0;

                // Add animations
                uint realFrameBase = 0;
                for (int j = 0; j < oldMoveable.Animations.Count; ++j)
                {
                    var oldAnimation    = oldMoveable.Animations[j];
                    var newAnimation    = new tr_animation();
                    var animationHelper = animationDictionary[oldAnimation];

                    // Calculate accelerations from velocities
                    int acceleration        = 0;
                    int lateralAcceleration = 0;
                    int speed        = 0;
                    int lateralSpeed = 0;

                    if (oldAnimation.KeyFrames.Count != 0 && oldAnimation.FrameRate != 0)
                    {
                        acceleration = (int)Math.Round((oldAnimation.EndVelocity - oldAnimation.StartVelocity) /
                                                       ((oldAnimation.KeyFrames.Count > 1 ? oldAnimation.KeyFrames.Count - 1 : 1) * oldAnimation.FrameRate) * 65536.0f);
                        lateralAcceleration = (int)Math.Round((oldAnimation.EndLateralVelocity - oldAnimation.StartLateralVelocity) /
                                                              ((oldAnimation.KeyFrames.Count > 1 ? oldAnimation.KeyFrames.Count - 1 : 1) * oldAnimation.FrameRate) * 65536.0f);
                    }
                    speed        = (int)Math.Round(oldAnimation.StartVelocity * 65536.0f);
                    lateralSpeed = (int)Math.Round(oldAnimation.StartLateralVelocity * 65536.0f);

                    // Clamp EndFrame to max. frame count as a last resort to prevent glitching animations.

                    var frameCount = oldAnimation.EndFrame + 1;
                    var maxFrame   = oldAnimation.GetRealNumberOfFrames(oldAnimation.KeyFrames.Count);
                    if (frameCount > maxFrame)
                    {
                        frameCount = maxFrame;
                    }

                    // Setup the final animation
                    if (j == 0)
                    {
                        newMoveable.FrameOffset = checked ((uint)animationHelper.KeyFrameOffset);
                    }
                    newAnimation.FrameOffset       = checked ((uint)animationHelper.KeyFrameOffset);
                    newAnimation.FrameRate         = oldAnimation.FrameRate;
                    newAnimation.FrameSize         = checked ((byte)animationHelper.KeyFrameSize);
                    newAnimation.Speed             = speed;
                    newAnimation.Accel             = acceleration;
                    newAnimation.SpeedLateral      = lateralSpeed;
                    newAnimation.AccelLateral      = lateralAcceleration;
                    newAnimation.FrameStart        = unchecked ((ushort)realFrameBase);
                    newAnimation.FrameEnd          = unchecked ((ushort)(realFrameBase + (frameCount == 0 ? 0 : frameCount - 1)));
                    newAnimation.AnimCommand       = checked ((ushort)_animCommands.Count);
                    newAnimation.StateChangeOffset = checked ((ushort)_stateChanges.Count);
                    newAnimation.NumAnimCommands   = checked ((ushort)oldAnimation.AnimCommands.Count);
                    newAnimation.NumStateChanges   = checked ((ushort)oldAnimation.StateChanges.Count);
                    newAnimation.NextAnimation     = checked ((ushort)(oldAnimation.NextAnimation + lastAnimation));
                    newAnimation.NextFrame         = oldAnimation.NextFrame;
                    newAnimation.StateID           = oldAnimation.StateId;

                    // Add anim commands
                    foreach (var command in oldAnimation.AnimCommands)
                    {
                        switch (command.Type)
                        {
                        case WadAnimCommandType.SetPosition:
                            _animCommands.Add(0x01);

                            _animCommands.Add(command.Parameter1);
                            _animCommands.Add(command.Parameter2);
                            _animCommands.Add(command.Parameter3);

                            break;

                        case WadAnimCommandType.SetJumpDistance:
                            _animCommands.Add(0x02);

                            _animCommands.Add(command.Parameter1);
                            _animCommands.Add(command.Parameter2);

                            break;

                        case WadAnimCommandType.EmptyHands:
                            _animCommands.Add(0x03);

                            break;

                        case WadAnimCommandType.KillEntity:
                            _animCommands.Add(0x04);

                            break;

                        case WadAnimCommandType.PlaySound:
                            _animCommands.Add(0x05);

                            _animCommands.Add(unchecked ((short)(command.Parameter1 + newAnimation.FrameStart)));
                            _animCommands.Add(unchecked ((short)(command.Parameter2)));

                            break;

                        case WadAnimCommandType.FlipEffect:
                            _animCommands.Add(0x06);

                            _animCommands.Add(checked ((short)(command.Parameter1 + newAnimation.FrameStart)));
                            _animCommands.Add(command.Parameter2);

                            break;
                        }
                    }

                    // Add state changes
                    foreach (var stateChange in oldAnimation.StateChanges)
                    {
                        var newStateChange = new tr_state_change();

                        newStateChange.AnimDispatch      = checked ((ushort)lastAnimDispatch);
                        newStateChange.StateID           = stateChange.StateId;
                        newStateChange.NumAnimDispatches = checked ((ushort)stateChange.Dispatches.Count);

                        foreach (var dispatch in stateChange.Dispatches)
                        {
                            var newAnimDispatch = new tr_anim_dispatch();

                            newAnimDispatch.Low           = unchecked ((ushort)(dispatch.InFrame + newAnimation.FrameStart));
                            newAnimDispatch.High          = unchecked ((ushort)(dispatch.OutFrame + newAnimation.FrameStart));
                            newAnimDispatch.NextAnimation = checked ((ushort)(dispatch.NextAnimation + lastAnimation));
                            newAnimDispatch.NextFrame     = dispatch.NextFrame;

                            _animDispatches.Add(newAnimDispatch);
                        }

                        lastAnimDispatch += stateChange.Dispatches.Count;

                        _stateChanges.Add(newStateChange);
                    }

                    _animations.Add(newAnimation);

                    realFrameBase += frameCount < 0 ? (ushort)0 : (ushort)frameCount; // FIXME: Not really needed?
                }
                lastAnimation += oldMoveable.Animations.Count;

                newMoveable.MeshTree     = (uint)_meshTrees.Count;
                newMoveable.StartingMesh = (ushort)_meshPointers.Count;

                for (int i = 0; i < oldMoveable.Meshes.Count; i++)
                {
                    var wadMesh = oldMoveable.Meshes[i];
                    ConvertWadMesh(wadMesh, false, (int)oldMoveable.Id.TypeId, i, oldMoveable.Id.IsWaterfall(_level.Settings.GameVersion), oldMoveable.Id.IsOptics(_level.Settings.GameVersion));
                }

                var meshTrees  = new List <tr_meshtree>();
                var usedMeshes = new List <WadMesh>();
                usedMeshes.Add(oldMoveable.Bones[0].Mesh);

                for (int b = 1; b < oldMoveable.Bones.Count; b++)
                {
                    tr_meshtree tree = new tr_meshtree();

                    tree.Opcode = (int)oldMoveable.Bones[b].OpCode;
                    tree.X      = (int)oldMoveable.Bones[b].Translation.X;
                    tree.Y      = (int)-oldMoveable.Bones[b].Translation.Y;
                    tree.Z      = (int)oldMoveable.Bones[b].Translation.Z;

                    usedMeshes.Add(oldMoveable.Bones[b].Mesh);
                    meshTrees.Add(tree);
                }

                foreach (var meshTree in meshTrees)
                {
                    _meshTrees.Add(meshTree.Opcode);
                    _meshTrees.Add(meshTree.X);
                    _meshTrees.Add(meshTree.Y);
                    _meshTrees.Add(meshTree.Z);
                }

                _moveables.Add(newMoveable);
            }

            // Adjust NextFrame of each Animation
            for (int i = 0; i < _animations.Count; i++)
            {
                var animation = _animations[i];
                animation.NextFrame += _animations[animation.NextAnimation].FrameStart;
                _animations[i]       = animation;
            }

            // Adjust NextFrame of each AnimDispatch
            for (int i = 0; i < _animDispatches.Count; i++)
            {
                var dispatch = _animDispatches[i];
                dispatch.NextFrame += _animations[dispatch.NextAnimation].FrameStart;
                _animDispatches[i]  = dispatch;
            }

            // Convert static meshes
            int convertedStaticsCount = 0;

            ReportProgress(10, "Converting static meshes");
            foreach (WadStatic oldStaticMesh in statics.Values)
            {
                var newStaticMesh = new tr_staticmesh();

                newStaticMesh.ObjectID = oldStaticMesh.Id.TypeId;

                newStaticMesh.CollisionBox = new tr_bounding_box
                {
                    X1 = (short)Math.Max(short.MinValue, Math.Min(short.MaxValue, oldStaticMesh.CollisionBox.Minimum.X)),
                    X2 = (short)Math.Max(short.MinValue, Math.Min(short.MaxValue, oldStaticMesh.CollisionBox.Maximum.X)),
                    Y1 = (short)Math.Max(short.MinValue, Math.Min(short.MaxValue, -oldStaticMesh.CollisionBox.Minimum.Y)),
                    Y2 = (short)Math.Max(short.MinValue, Math.Min(short.MaxValue, -oldStaticMesh.CollisionBox.Maximum.Y)),
                    Z1 = (short)Math.Max(short.MinValue, Math.Min(short.MaxValue, oldStaticMesh.CollisionBox.Minimum.Z)),
                    Z2 = (short)Math.Max(short.MinValue, Math.Min(short.MaxValue, oldStaticMesh.CollisionBox.Maximum.Z))
                };

                newStaticMesh.VisibilityBox = new tr_bounding_box
                {
                    X1 = (short)Math.Max(short.MinValue, Math.Min(short.MaxValue, oldStaticMesh.VisibilityBox.Minimum.X)),
                    X2 = (short)Math.Max(short.MinValue, Math.Min(short.MaxValue, oldStaticMesh.VisibilityBox.Maximum.X)),
                    Y1 = (short)Math.Max(short.MinValue, Math.Min(short.MaxValue, -oldStaticMesh.VisibilityBox.Minimum.Y)),
                    Y2 = (short)Math.Max(short.MinValue, Math.Min(short.MaxValue, -oldStaticMesh.VisibilityBox.Maximum.Y)),
                    Z1 = (short)Math.Max(short.MinValue, Math.Min(short.MaxValue, oldStaticMesh.VisibilityBox.Minimum.Z)),
                    Z2 = (short)Math.Max(short.MinValue, Math.Min(short.MaxValue, oldStaticMesh.VisibilityBox.Maximum.Z))
                };

                if (_level.Settings.GameVersion > TRVersion.Game.TR3)
                {
                    newStaticMesh.Flags = (ushort)oldStaticMesh.Flags;
                }
                else
                {
                    newStaticMesh.Flags = 2; // bit 0: no collision, bit 1: visibility
                }
                newStaticMesh.Mesh = (ushort)_meshPointers.Count;

                // Do not add faces and vertices to the wad, instead keep only the bounding boxes when we automatically merge the Mesh
                if (_level.Settings.FastMode || !_level.Settings.AutoStaticMeshMergeContainsStaticMesh(oldStaticMesh))
                {
                    ConvertWadMesh(oldStaticMesh.Mesh, true, (int)oldStaticMesh.Id.TypeId, 0, false, false, oldStaticMesh.LightingType);
                }
                else
                {
                    convertedStaticsCount++;
                    logger.Info("Creating Dummy Mesh for automatically Merged Mesh: " + oldStaticMesh.ToString(_level.Settings.GameVersion));
                    CreateDummyWadMesh(oldStaticMesh.Mesh, true, (int)oldStaticMesh.Id.TypeId, false, false, oldStaticMesh.LightingType);
                }
                _staticMeshes.Add(newStaticMesh);
            }

            if (convertedStaticsCount > 0)
            {
                _progressReporter.ReportInfo("    Number of statics merged with room geometry: " + convertedStaticsCount);
            }
            else
            {
                _progressReporter.ReportInfo("    No statics to merge into room geometry.");
            }

            if (_writeDbgWadTxt)
            {
                using (var fileStream = new FileStream("Wad.txt", FileMode.Create, FileAccess.Write, FileShare.None))
                    using (var writer = new StreamWriter(fileStream))
                    {
                        int n = 0;
                        foreach (var anim in _animations)
                        {
                            writer.WriteLine("Anim #" + n);
                            writer.WriteLine("    KeyframeOffset: " + anim.FrameOffset);
                            writer.WriteLine("    FrameRate: " + anim.FrameRate);
                            writer.WriteLine("    KeyFrameSize: " + anim.FrameSize);
                            writer.WriteLine("    FrameStart: " + anim.FrameStart);
                            writer.WriteLine("    FrameEnd: " + anim.FrameEnd);
                            writer.WriteLine("    StateChangeOffset: " + anim.StateChangeOffset);
                            writer.WriteLine("    NumStateChanges: " + anim.NumStateChanges);
                            writer.WriteLine("    AnimCommand: " + anim.AnimCommand);
                            writer.WriteLine("    NumAnimCommands: " + anim.NumAnimCommands);
                            writer.WriteLine("    NextAnimation: " + anim.NextAnimation);
                            writer.WriteLine("    NextFrame: " + anim.NextFrame);
                            writer.WriteLine("    StateID: " + anim.StateID);
                            writer.WriteLine("    Speed: " + anim.Speed.ToString("X"));
                            writer.WriteLine("    Accel: " + anim.Accel.ToString("X"));
                            writer.WriteLine("    SpeedLateral: " + anim.SpeedLateral.ToString("X"));
                            writer.WriteLine("    AccelLateral: " + anim.AccelLateral.ToString("X"));
                            writer.WriteLine();

                            n++;
                        }

                        n = 0;
                        foreach (var dispatch in _animDispatches)
                        {
                            writer.WriteLine("AnimDispatch #" + n);
                            writer.WriteLine("    In: " + dispatch.Low);
                            writer.WriteLine("    Out: " + dispatch.High);
                            writer.WriteLine("    NextAnimation: " + dispatch.NextAnimation);
                            writer.WriteLine("    NextFrame: " + dispatch.NextFrame);
                            writer.WriteLine();

                            n++;
                        }

                        n = 0;
                        for (int jj = 0; jj < _meshTrees.Count; jj += 4)
                        {
                            writer.WriteLine("MeshTree #" + jj);
                            writer.WriteLine("    Op: " + _meshTrees[jj + 0]);
                            writer.WriteLine("    X: " + _meshTrees[jj + 1]);
                            writer.WriteLine("    Y: " + _meshTrees[jj + 2]);
                            writer.WriteLine("    Z: " + _meshTrees[jj + 3]);
                            writer.WriteLine();

                            n++;
                        }

                        n = 0;
                        for (int jj = 0; jj < _meshPointers.Count; jj++)
                        {
                            writer.WriteLine("MeshPointer #" + jj + ": " + _meshPointers[jj]);

                            n++;
                        }

                        n = 0;
                        foreach (var mesh in _meshes)
                        {
                            writer.WriteLine("Mesh #" + n);
                            writer.WriteLine("    Vertices: " + mesh.NumVertices);
                            writer.WriteLine("    Normals: " + mesh.NumNormals);
                            writer.WriteLine("    Polygons: " + (mesh.NumTexturedQuads + mesh.NumTexturedTriangles));
                            writer.WriteLine("    MeshPointer: " + mesh.MeshPointer);
                            writer.WriteLine();

                            n++;
                        }

                        n = 0;
                        foreach (var mov in _moveables)
                        {
                            writer.WriteLine("Moveable #" + n);
                            writer.WriteLine("    MeshTree: " + mov.MeshTree);
                            writer.WriteLine("    MeshPointer: " + mov.StartingMesh);
                            writer.WriteLine("    AnimationIndex: " + mov.Animation);
                            writer.WriteLine("    NumMeshes: " + mov.NumMeshes);
                            writer.WriteLine();

                            n++;
                        }

                        n = 0;
                        foreach (var sc in _stateChanges)
                        {
                            writer.WriteLine("StateChange #" + n);
                            writer.WriteLine("    StateID: " + sc.StateID);
                            writer.WriteLine("    NumAnimDispatches: " + sc.NumAnimDispatches);
                            writer.WriteLine("    AnimDispatch: " + sc.AnimDispatch);
                            writer.WriteLine();

                            n++;
                        }
                    }
            }
        }
예제 #5
0
        internal static WadMoveable ConvertTr4MoveableToWadMoveable(Wad2 wad, Tr4Wad oldWad, int moveableIndex,
                                                                    Dictionary <int, WadTexture> textures)
        {
            wad_moveable oldMoveable = oldWad.Moveables[moveableIndex];
            var          newId       = new WadMoveableId(oldMoveable.ObjectID);

            // A workaround to find out duplicated item IDs produced by StrPix unpatched for v130 wads.
            // In such case, a legacy name is used to guess real item ID, if this fails - broken item is filtered out.
            if (wad.Moveables.ContainsKey(newId))
            {
                var message = "Duplicated moveable ID " + oldMoveable.ObjectID + " was identified while loading " + oldWad.BaseName + ". ";
                if (oldWad.LegacyNames.Count - oldWad.Statics.Count < moveableIndex)
                {
                    logger.Warn(message + "Can't restore real ID by name, name table is too short. Ignoring moveable.");
                    return(null);
                }

                bool isMoveable;
                var  guessedId = TrCatalog.GetItemIndex(TRVersion.Game.TR4, oldWad.LegacyNames[moveableIndex], out isMoveable);

                if (isMoveable && guessedId.HasValue)
                {
                    newId = new WadMoveableId(guessedId.Value);
                    if (wad.Moveables.ContainsKey(newId))
                    {
                        logger.Warn(message + "Can't restore real ID by name, name table is inconsistent. Ignoring moveable.");
                        return(null);
                    }
                    else
                    {
                        logger.Warn(message + "Successfully restored real ID by name.");
                    }
                }
                else
                {
                    logger.Warn(message + "Can't find provided name in catalog. Ignoring moveable.");
                    return(null);
                }
            }

            WadMoveable newMoveable = new WadMoveable(newId);
            var         frameBases  = new Dictionary <WadAnimation, ushort[]>();

            // Load meshes
            var meshes = new List <WadMesh>();

            for (int j = 0; j < oldMoveable.NumPointers; j++)
            {
                meshes.Add(ConvertTr4MeshToWadMesh(wad, oldWad, textures,
                                                   oldWad.Meshes[(int)oldWad.RealPointers[oldMoveable.PointerIndex + j]],
                                                   (int)oldMoveable.ObjectID));
            }

            // Build the skeleton
            var root = new WadBone();

            root.Name        = "bone_0_root";
            root.Parent      = null;
            root.Translation = Vector3.Zero;
            root.Mesh        = meshes[0];

            newMoveable.Bones.Add(root);

            for (int j = 0; j < oldMoveable.NumPointers - 1; j++)
            {
                WadBone bone = new WadBone();
                bone.Name        = "bone_" + (j + 1).ToString();
                bone.Parent      = null;
                bone.Translation = Vector3.Zero;
                bone.Mesh        = meshes[j + 1];
                newMoveable.Bones.Add(bone);
            }

            for (int mi = 0; mi < (oldMoveable.NumPointers - 1); mi++)
            {
                int j = mi + 1;

                var opcode = (WadLinkOpcode)oldWad.Links[(int)(oldMoveable.LinksIndex + mi * 4)];
                int linkX  = oldWad.Links[(int)(oldMoveable.LinksIndex + mi * 4) + 1];
                int linkY  = -oldWad.Links[(int)(oldMoveable.LinksIndex + mi * 4) + 2];
                int linkZ  = oldWad.Links[(int)(oldMoveable.LinksIndex + mi * 4) + 3];

                newMoveable.Bones[j].OpCode      = opcode;
                newMoveable.Bones[j].Translation = new Vector3(linkX, linkY, linkZ);
            }

            // Convert animations
            int numAnimations = 0;
            int nextMoveable  = oldWad.GetNextMoveableWithAnimations(moveableIndex);

            if (nextMoveable == -1)
            {
                numAnimations = oldWad.Animations.Count - oldMoveable.AnimationIndex;
            }
            else
            {
                numAnimations = oldWad.Moveables[nextMoveable].AnimationIndex - oldMoveable.AnimationIndex;
            }

            for (int j = 0; j < numAnimations; j++)
            {
                if (oldMoveable.AnimationIndex == -1)
                {
                    break;
                }

                wad_animation oldAnimation = oldWad.Animations[j + oldMoveable.AnimationIndex];
                WadAnimation  newAnimation = new WadAnimation();
                newAnimation.StateId       = oldAnimation.StateId;
                newAnimation.FrameRate     = oldAnimation.FrameDuration;
                newAnimation.NextAnimation = (ushort)(oldAnimation.NextAnimation - oldMoveable.AnimationIndex);
                newAnimation.NextFrame     = oldAnimation.NextFrame;
                newAnimation.Name          = TrCatalog.GetAnimationName(TRVersion.Game.TR4, oldMoveable.ObjectID, (uint)j);

                // Fix wadmerger/wad format bug with inverted frame start/end on single-frame anims
                ushort newFrameStart = oldAnimation.FrameStart < oldAnimation.FrameEnd ? oldAnimation.FrameStart : oldAnimation.FrameEnd;
                ushort newFrameEnd   = oldAnimation.FrameStart < oldAnimation.FrameEnd ? oldAnimation.FrameEnd : newFrameStart;
                newAnimation.EndFrame = (ushort)(newFrameEnd - newFrameStart);

                for (int k = 0; k < oldAnimation.NumStateChanges; k++)
                {
                    WadStateChange   sc    = new WadStateChange();
                    wad_state_change wadSc = oldWad.Changes[(int)oldAnimation.ChangesIndex + k];
                    sc.StateId = (ushort)wadSc.StateId;

                    for (int n = 0; n < wadSc.NumDispatches; n++)
                    {
                        WadAnimDispatch   ad    = new WadAnimDispatch();
                        wad_anim_dispatch wadAd = oldWad.Dispatches[(int)wadSc.DispatchesIndex + n];

                        ad.InFrame       = (ushort)(wadAd.Low - newFrameStart);
                        ad.OutFrame      = (ushort)(wadAd.High - newFrameStart);
                        ad.NextAnimation = (ushort)((wadAd.NextAnimation - oldMoveable.AnimationIndex) % numAnimations);
                        ad.NextFrame     = (ushort)wadAd.NextFrame;

                        sc.Dispatches.Add(ad);
                    }

                    newAnimation.StateChanges.Add(sc);
                }

                if (oldAnimation.NumCommands < oldWad.Commands.Count)
                {
                    int lastCommand = oldAnimation.CommandOffset;

                    for (int k = 0; k < oldAnimation.NumCommands; k++)
                    {
                        short commandType = oldWad.Commands[lastCommand];

                        WadAnimCommand command = new WadAnimCommand();
                        command.Type = (WadAnimCommandType)commandType;
                        switch (command.Type)
                        {
                        case WadAnimCommandType.SetPosition:
                            command.Parameter1 = (short)oldWad.Commands[lastCommand + 1];
                            command.Parameter2 = (short)oldWad.Commands[lastCommand + 2];
                            command.Parameter3 = (short)oldWad.Commands[lastCommand + 3];
                            lastCommand       += 4;
                            break;

                        case WadAnimCommandType.SetJumpDistance:
                            command.Parameter1 = (short)oldWad.Commands[lastCommand + 1];
                            command.Parameter2 = (short)oldWad.Commands[lastCommand + 2];
                            lastCommand       += 3;
                            break;

                        case WadAnimCommandType.EmptyHands:
                        case WadAnimCommandType.KillEntity:
                            lastCommand += 1;
                            break;

                        case WadAnimCommandType.PlaySound:
                        case WadAnimCommandType.FlipEffect:
                            command.Parameter1 = (short)(oldWad.Commands[lastCommand + 1] - newFrameStart);
                            command.Parameter2 = (short)oldWad.Commands[lastCommand + 2];

                            // For single-frame anims, clamp frame number to first frame (another fix for WM/wad format range inversion bug)
                            if (newAnimation.EndFrame == 0 && command.Parameter1 > 0)
                            {
                                command.Parameter1 = 0;
                            }

                            lastCommand += 3;
                            break;

                        default:     // Ignore invalid anim commands (see for example karnak.wad)
                            logger.Warn("Invalid anim command " + commandType);
                            goto ExitForLoop;
                        }

                        newAnimation.AnimCommands.Add(command);
                    }
ExitForLoop:
                    ;
                }

                int  frames    = (int)oldAnimation.KeyFrameOffset / 2;
                uint numFrames = 0;
                if (oldAnimation.KeyFrameSize != 0)
                {
                    if ((j + oldMoveable.AnimationIndex) == (oldWad.Animations.Count - 1))
                    {
                        numFrames = ((uint)(2 * oldWad.KeyFrames.Count) - oldAnimation.KeyFrameOffset) / (uint)(2 * oldAnimation.KeyFrameSize);
                    }
                    else
                    {
                        numFrames = (oldWad.Animations[oldMoveable.AnimationIndex + j + 1].KeyFrameOffset - oldAnimation.KeyFrameOffset) / (uint)(2 * oldAnimation.KeyFrameSize);
                    }
                }

                for (int f = 0; f < numFrames; f++)
                {
                    WadKeyFrame frame        = new WadKeyFrame();
                    int         startOfFrame = frames;

                    frame.BoundingBox = new BoundingBox(new Vector3(oldWad.KeyFrames[frames],
                                                                    -oldWad.KeyFrames[frames + 2],
                                                                    oldWad.KeyFrames[frames + 4]),
                                                        new Vector3(oldWad.KeyFrames[frames + 1],
                                                                    -oldWad.KeyFrames[frames + 3],
                                                                    oldWad.KeyFrames[frames + 5]));
                    frames += 6;

                    frame.Offset = new Vector3(oldWad.KeyFrames[frames],
                                               (short)(-oldWad.KeyFrames[frames + 1]),
                                               oldWad.KeyFrames[frames + 2]);
                    frames += 3;

                    for (int n = 0; n < oldMoveable.NumPointers; n++)
                    {
                        frame.Angles.Add(WadKeyFrameRotation.FromTrAngle(ref frames, oldWad.KeyFrames, false, true));
                    }
                    if ((frames - startOfFrame) < oldAnimation.KeyFrameSize)
                    {
                        frames += ((int)oldAnimation.KeyFrameSize - (frames - startOfFrame));
                    }

                    newAnimation.KeyFrames.Add(frame);
                }

                // New velocities
                float acceleration = oldAnimation.Accel / 65536.0f;
                newAnimation.StartVelocity = oldAnimation.Speed / 65536.0f;

                float lateralAcceleration = oldAnimation.AccelLateral / 65536.0f;
                newAnimation.StartLateralVelocity = oldAnimation.SpeedLateral / 65536.0f;

                if (newAnimation.KeyFrames.Count != 0 && newAnimation.FrameRate != 0)
                {
                    newAnimation.EndVelocity = newAnimation.StartVelocity + acceleration *
                                               (newAnimation.KeyFrames.Count - 1) * newAnimation.FrameRate;
                    newAnimation.EndLateralVelocity = newAnimation.StartLateralVelocity + lateralAcceleration *
                                                      (newAnimation.KeyFrames.Count - 1) * newAnimation.FrameRate;
                }
                else
                {
                    // Basic foolproofness for potentially broken animations
                    newAnimation.EndVelocity        = newAnimation.StartVelocity;
                    newAnimation.EndLateralVelocity = newAnimation.StartLateralVelocity;
                }

                // Deduce real maximum frame number, based on interpolation and keyframes.
                // We need to refer this value in NextFrame-related fixes (below) because of epic WadMerger bug,
                // which incorrectly calculates NextFrame and "steals" last frame from every custom animation.
                ushort maxFrameCount = (ushort)((newAnimation.FrameRate == 1 || numFrames <= 2) ? numFrames : ((numFrames - 1) * newAnimation.FrameRate) + 1);

                // Also correct animation out-point
                if (newAnimation.EndFrame >= maxFrameCount)
                {
                    newAnimation.EndFrame = (ushort)(maxFrameCount - 1);
                }

                frameBases.Add(newAnimation, new ushort[] { newFrameStart, (ushort)(maxFrameCount - 1) });
                newMoveable.Animations.Add(newAnimation);
            }

            for (int i = 0; i < newMoveable.Animations.Count; i++)
            {
                var animation = newMoveable.Animations[i];

                if (animation.KeyFrames.Count == 0)
                {
                    animation.EndFrame = 0;
                }

                // HACK: this fixes some invalid NextAnimations values
                animation.NextAnimation %= (ushort)newMoveable.Animations.Count;

                newMoveable.Animations[i] = animation;
            }

            for (int i = 0; i < newMoveable.Animations.Count; i++)
            {
                var animation = newMoveable.Animations[i];

                // HACK: this fixes some invalid NextFrame values
                if (frameBases[newMoveable.Animations[animation.NextAnimation]][0] != 0)
                {
                    animation.NextFrame -= frameBases[newMoveable.Animations[animation.NextAnimation]][0];
                    if (animation.NextFrame > frameBases[newMoveable.Animations[animation.NextAnimation]][1])
                    {
                        animation.NextFrame = frameBases[newMoveable.Animations[animation.NextAnimation]][1];
                    }
                }

                foreach (var stateChange in animation.StateChanges)
                {
                    for (int j = 0; j < stateChange.Dispatches.Count; ++j)
                    {
                        WadAnimDispatch animDispatch = stateChange.Dispatches[j];

                        // HACK: Probably WadMerger's bug
                        if (animDispatch.NextAnimation > 32767)
                        {
                            animDispatch.NextAnimation = 0;
                            animDispatch.NextFrame     = 0;
                            continue;
                        }

                        if (frameBases[newMoveable.Animations[animDispatch.NextAnimation]][0] != 0)
                        {
                            // HACK: In some cases dispatches have invalid NextFrame.
                            // From tests it seems that's ok to make NextFrame equal to max frame number.
                            animDispatch.NextFrame -= frameBases[newMoveable.Animations[animDispatch.NextAnimation]][0];
                            if (animDispatch.NextFrame > frameBases[newMoveable.Animations[animDispatch.NextAnimation]][1])
                            {
                                animDispatch.NextFrame = frameBases[newMoveable.Animations[animDispatch.NextAnimation]][1];
                            }
                        }
                        stateChange.Dispatches[j] = animDispatch;
                    }
                }
            }

            wad.Moveables.Add(newMoveable.Id, newMoveable);
            return(newMoveable);
        }
예제 #6
0
        public static WadMoveable ConvertTrLevelMoveableToWadMoveable(Wad2 wad, TrLevel oldLevel, int moveableIndex,
                                                                      TextureArea[] objectTextures)
        {
            Console.WriteLine("Converting Moveable " + moveableIndex);

            var         oldMoveable = oldLevel.Moveables[moveableIndex];
            WadMoveable newMoveable = new WadMoveable(new WadMoveableId(oldMoveable.ObjectID));

            // First a list of meshes for this moveable is built
            var oldMeshes = new List <tr_mesh>();

            for (int j = 0; j < oldMoveable.NumMeshes; j++)
            {
                oldMeshes.Add(oldLevel.Meshes[(int)oldLevel.RealPointers[(int)(oldMoveable.StartingMesh + j)]]);
            }

            // Convert the WadMesh
            var newMeshes = new List <WadMesh>();

            foreach (var oldMesh in oldMeshes)
            {
                newMeshes.Add(ConvertTrLevelMeshToWadMesh(wad, oldLevel, oldMesh, objectTextures));
            }

            // Build the skeleton
            var root = new WadBone();

            root.Name        = "bone_0_root";
            root.Parent      = null;
            root.Translation = Vector3.Zero;
            root.Mesh        = newMeshes[0];

            newMoveable.Bones.Add(root);

            for (int j = 0; j < oldMoveable.NumMeshes - 1; j++)
            {
                var bone = new WadBone();
                bone.Name        = "bone_" + (j + 1).ToString();
                bone.Parent      = null;
                bone.Translation = Vector3.Zero;
                bone.Mesh        = newMeshes[j + 1];
                newMoveable.Bones.Add(bone);
            }

            for (int mi = 0; mi < (oldMeshes.Count - 1); mi++)
            {
                int j = mi + 1;

                var opcode = (WadLinkOpcode)oldLevel.MeshTrees[(int)(oldMoveable.MeshTree + mi * 4)];
                int linkX  = oldLevel.MeshTrees[(int)(oldMoveable.MeshTree + mi * 4) + 1];
                int linkY  = -oldLevel.MeshTrees[(int)(oldMoveable.MeshTree + mi * 4) + 2];
                int linkZ  = oldLevel.MeshTrees[(int)(oldMoveable.MeshTree + mi * 4) + 3];

                newMoveable.Bones[j].OpCode      = opcode;
                newMoveable.Bones[j].Translation = new Vector3(linkX, linkY, linkZ);
            }

            // Convert animations
            int numAnimations = 0;
            int nextMoveable  = oldLevel.GetNextMoveableWithAnimations(moveableIndex);

            if (nextMoveable == -1)
            {
                numAnimations = oldLevel.Animations.Count - oldMoveable.Animation;
            }
            else
            {
                numAnimations = oldLevel.Moveables[nextMoveable].Animation - oldMoveable.Animation;
            }

            var frameBases = new Dictionary <WadAnimation, ushort>();

            for (int j = 0; j < numAnimations; j++)
            {
                if (oldMoveable.Animation == -1)
                {
                    break;
                }

                WadAnimation newAnimation = new WadAnimation();
                var          oldAnimation = oldLevel.Animations[j + oldMoveable.Animation];
                newAnimation.FrameRate     = oldAnimation.FrameRate;
                newAnimation.NextAnimation = (ushort)(oldAnimation.NextAnimation - oldMoveable.Animation);
                newAnimation.NextFrame     = oldAnimation.NextFrame;
                newAnimation.StateId       = oldAnimation.StateID;
                newAnimation.EndFrame      = (ushort)(oldAnimation.FrameEnd - oldAnimation.FrameStart);
                newAnimation.Name          = TrCatalog.GetAnimationName(oldLevel.Version, oldMoveable.ObjectID, (uint)j);

                for (int k = 0; k < oldAnimation.NumStateChanges; k++)
                {
                    WadStateChange sc    = new WadStateChange();
                    var            wadSc = oldLevel.StateChanges[(int)oldAnimation.StateChangeOffset + k];
                    sc.StateId = wadSc.StateID;

                    for (int n = 0; n < wadSc.NumAnimDispatches; n++)
                    {
                        WadAnimDispatch ad    = new WadAnimDispatch();
                        var             wadAd = oldLevel.AnimDispatches[(int)wadSc.AnimDispatch + n];

                        ad.InFrame       = (ushort)(wadAd.Low - oldAnimation.FrameStart);
                        ad.OutFrame      = (ushort)(wadAd.High - oldAnimation.FrameStart);
                        ad.NextAnimation = (ushort)((wadAd.NextAnimation - oldMoveable.Animation) % numAnimations);
                        ad.NextFrame     = (ushort)wadAd.NextFrame;

                        sc.Dispatches.Add(ad);
                    }

                    newAnimation.StateChanges.Add(sc);
                }

                if (oldAnimation.NumAnimCommands < oldLevel.AnimCommands.Count)
                {
                    int lastCommand = oldAnimation.AnimCommand;

                    for (int k = 0; k < oldAnimation.NumAnimCommands; k++)
                    {
                        short commandType = oldLevel.AnimCommands[lastCommand + 0];

                        WadAnimCommand command = new WadAnimCommand {
                            Type = (WadAnimCommandType)commandType
                        };
                        switch (commandType)
                        {
                        case 1:
                            command.Parameter1 = (short)oldLevel.AnimCommands[lastCommand + 1];
                            command.Parameter2 = (short)oldLevel.AnimCommands[lastCommand + 2];
                            command.Parameter3 = (short)oldLevel.AnimCommands[lastCommand + 3];

                            lastCommand += 4;
                            break;

                        case 2:
                            command.Parameter1 = (short)oldLevel.AnimCommands[lastCommand + 1];
                            command.Parameter2 = (short)oldLevel.AnimCommands[lastCommand + 2];

                            lastCommand += 3;
                            break;

                        case 3:
                            lastCommand += 1;
                            break;

                        case 4:
                            lastCommand += 1;
                            break;

                        case 5:
                            command.Parameter1 = (short)(oldLevel.AnimCommands[lastCommand + 1] - oldAnimation.FrameStart);
                            command.Parameter2 = (short)oldLevel.AnimCommands[lastCommand + 2];
                            lastCommand       += 3;
                            break;

                        case 6:
                            command.Parameter1 = (short)(oldLevel.AnimCommands[lastCommand + 1] - oldAnimation.FrameStart);
                            command.Parameter2 = (short)oldLevel.AnimCommands[lastCommand + 2];
                            lastCommand       += 3;
                            break;

                        default:     // Ignore invalid anim commands (see for example karnak.wad)
                            logger.Warn("Unknown anim command " + commandType);
                            goto ExitForLoop;
                        }

                        newAnimation.AnimCommands.Add(command);
                    }
ExitForLoop:
                    ;
                }

                int  frames = (int)oldAnimation.FrameOffset / 2;
                uint numFrames;

                if (j + oldMoveable.Animation == oldLevel.Animations.Count - 1)
                {
                    if (oldAnimation.FrameSize == 0)
                    {
                        numFrames = oldLevel.Version == TRVersion.Game.TR1 ? (uint)((newAnimation.EndFrame + 1) / newAnimation.FrameRate) : 0;
                    }
                    else
                    {
                        numFrames = ((uint)(2 * oldLevel.Frames.Count) - oldAnimation.FrameOffset) / (uint)(2 * oldAnimation.FrameSize);
                    }
                }
                else
                {
                    if (oldAnimation.FrameSize == 0)
                    {
                        numFrames = oldLevel.Version == TRVersion.Game.TR1 ? (uint)((newAnimation.EndFrame + 1) / newAnimation.FrameRate) : 0;
                    }
                    else
                    {
                        numFrames = (oldLevel.Animations[oldMoveable.Animation + j + 1].FrameOffset - oldAnimation.FrameOffset) / (uint)(2 * oldAnimation.FrameSize);
                    }
                }

                for (int f = 0; f < numFrames; f++)
                {
                    WadKeyFrame frame        = new WadKeyFrame();
                    int         startOfFrame = frames;

                    frame.BoundingBox = new BoundingBox(new Vector3(oldLevel.Frames[frames],
                                                                    -oldLevel.Frames[frames + 2],
                                                                    oldLevel.Frames[frames + 4]),
                                                        new Vector3(oldLevel.Frames[frames + 1],
                                                                    -oldLevel.Frames[frames + 3],
                                                                    oldLevel.Frames[frames + 5]));

                    frames += 6;

                    frame.Offset = new Vector3(oldLevel.Frames[frames],
                                               (short)(-oldLevel.Frames[frames + 1]),
                                               oldLevel.Frames[frames + 2]);

                    frames += 3;

                    // TR1 has also the number of angles to follow
                    if (oldLevel.Version == TRVersion.Game.TR1)
                    {
                        frames++;
                    }

                    for (int n = 0; n < oldMoveable.NumMeshes; n++)
                    {
                        frame.Angles.Add(
                            WadKeyFrameRotation.FromTrAngle(ref frames, oldLevel.Frames,
                                                            oldLevel.Version == TRVersion.Game.TR1,
                                                            oldLevel.Version == TRVersion.Game.TR4 || oldLevel.Version == TRVersion.Game.TR5));
                    }

                    if ((frames - startOfFrame) < oldAnimation.FrameSize)
                    {
                        frames += ((int)oldAnimation.FrameSize - (frames - startOfFrame));
                    }

                    newAnimation.KeyFrames.Add(frame);
                }

                frameBases.Add(newAnimation, oldAnimation.FrameStart);

                // New velocities
                float acceleration = oldAnimation.Accel / 65536.0f;
                newAnimation.StartVelocity = oldAnimation.Speed / 65536.0f;

                float lateralAcceleration = oldAnimation.AccelLateral / 65536.0f;
                newAnimation.StartLateralVelocity = oldAnimation.SpeedLateral / 65536.0f;

                if (newAnimation.KeyFrames.Count != 0 && newAnimation.FrameRate != 0)
                {
                    newAnimation.EndVelocity = newAnimation.StartVelocity + acceleration *
                                               (newAnimation.KeyFrames.Count - 1) * newAnimation.FrameRate;
                    newAnimation.EndLateralVelocity = newAnimation.StartLateralVelocity + lateralAcceleration *
                                                      (newAnimation.KeyFrames.Count - 1) * newAnimation.FrameRate;
                }

                newMoveable.Animations.Add(newAnimation);
            }

            for (int i = 0; i < newMoveable.Animations.Count; i++)
            {
                var animation = newMoveable.Animations[i];

                if (animation.KeyFrames.Count == 0)
                {
                    animation.EndFrame = 0;
                }

                // HACK: this fixes some invalid NextAnimations values
                animation.NextAnimation %= (ushort)newMoveable.Animations.Count;

                newMoveable.Animations[i] = animation;
            }

            for (int i = 0; i < newMoveable.Animations.Count; i++)
            {
                var animation = newMoveable.Animations[i];

                // HACK: this fixes some invalid NextFrame values
                if (frameBases[newMoveable.Animations[animation.NextAnimation]] != 0)
                {
                    animation.NextFrame %= frameBases[newMoveable.Animations[animation.NextAnimation]];
                }

                foreach (var stateChange in animation.StateChanges)
                {
                    for (int J = 0; J < stateChange.Dispatches.Count; ++J)
                    {
                        WadAnimDispatch animDispatch = stateChange.Dispatches[J];
                        if (frameBases[newMoveable.Animations[animDispatch.NextAnimation]] != 0)
                        {
                            ushort newFrame = (ushort)(animDispatch.NextFrame % frameBases[newMoveable.Animations[animDispatch.NextAnimation]]);

                            // In some cases dispatches have invalid NextFrame.
                            // From tests it seems that's ok to delete the dispatch or put the NextFrame equal to zero.
                            if (newFrame > newMoveable.Animations[animDispatch.NextAnimation].EndFrame)
                            {
                                newFrame = 0;
                            }

                            animDispatch.NextFrame = newFrame;
                        }
                        stateChange.Dispatches[J] = animDispatch;
                    }
                }

                newMoveable.Animations[i] = animation;
            }

            return(newMoveable);
        }