private void BuildMeshTree(WadBone bone, List <tr_meshtree> meshTrees, List <WadMesh> usedMeshes) { tr_meshtree tree = new tr_meshtree(); tree.X = (int)bone.Translation.X; tree.Y = (int)-bone.Translation.Y; tree.Z = (int)bone.Translation.Z; if (bone.Parent == null) { tree.Opcode = 2; } else { if (bone.Parent.Children.Count == 1) { tree.Opcode = 0; } else { int childrenCount = bone.Parent.Children.Count; if (bone.Parent.Children.IndexOf(bone) == 0) { tree.Opcode = 2; } else if (bone.Parent.Children.IndexOf(bone) == childrenCount - 1) { tree.Opcode = 1; } else { tree.Opcode = 3; } } } if (bone.Parent != null) { meshTrees.Add(tree); } usedMeshes.Add(bone.Mesh); for (int i = 0; i < bone.Children.Count; i++) { BuildMeshTree(bone.Children[i], meshTrees, usedMeshes); } }
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++; } } } }