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); }
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); }
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); }
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++; } } } }
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); }
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); }