public static void GenerateAnimCommandsTransform(SkeletalModel model) { if(EngineWorld.AnimCommands.Length == 0) { return; } //Sys.DebugLog("anim_transform.txt", "MODEL[{0}]", model.ID); for (var anim = 0; anim < model.Animations.Count; anim++) { if(model.Animations[anim].NumAnimCommands > 255) { continue; // If no anim commands or current anim has more than 255 (according to TRosettaStone). } var af = model.Animations[anim]; if (af.NumAnimCommands == 0) continue; Assert(af.AnimCommand < EngineWorld.AnimCommands.Length); var ac = (int) af.AnimCommand; for (var i = 0; i < af.NumAnimCommands; i++) { var command = EngineWorld.AnimCommands[ac]; ac++; switch((TR_ANIMCOMMAND)command) { case TR_ANIMCOMMAND.SetPosition: // This command executes ONLY at the end of animation. af.Frames.Last().Move.X = EngineWorld.AnimCommands[ac + 0]; // x = x af.Frames.Last().Move.Z = -EngineWorld.AnimCommands[ac + 1]; // z = -y af.Frames.Last().Move.Y = EngineWorld.AnimCommands[ac + 2]; // y = z af.Frames.Last().Command |= (ushort)ANIM_CMD.Move; ac += 3; break; case TR_ANIMCOMMAND.JumpDistance: af.Frames.Last().V_Vertical= EngineWorld.AnimCommands[ac + 0]; af.Frames.Last().V_Horizontal = -EngineWorld.AnimCommands[ac + 1]; af.Frames.Last().Command |= (ushort) ANIM_CMD.Jump; ac += 2; break; case TR_ANIMCOMMAND.EmptyHands: break; case TR_ANIMCOMMAND.Kill: break; case TR_ANIMCOMMAND.PlaySound: ac += 2; break; case TR_ANIMCOMMAND.PlayEffect: switch(EngineWorld.AnimCommands[ac + 1] & 0x3FFF) { case (int)TR_EFFECT.ChangeDirection: af.Frames.Last().Command |= (ushort) ANIM_CMD.ChangeDirection; ConsoleInfo.Instance.Printf("ROTATE: anim = {0}, frame = {1} of {2}", anim, EngineWorld.AnimCommands[ac + 0], af.Frames.Count); break; } ac += 2; break; } } } }
public static void TR_GenSkeletalModel(World world, int model_num, SkeletalModel model, Level tr) { var trMoveable = tr.Moveables[model_num]; // original tr structure model.CollisionMap.Resize(model.MeshCount); for (ushort i = 0; i < model.MeshCount; i++) { model.CollisionMap[i] = i; } model.MeshTree.Resize(model.MeshCount, () => new MeshTreeTag()); var treeTag = model.MeshTree[0]; var meshIndex = tr.MeshIndices.SkipEx(trMoveable.StartingMesh); for (var k = 0; k < model.MeshCount; k++) { treeTag = model.MeshTree[k]; treeTag.MeshBase = world.Meshes[(int) meshIndex[k]]; treeTag.MeshSkin = null; // PARANOID: I use calloc for tree_tag's treeTag.ReplaceAnim = 0x00; treeTag.ReplaceMesh = 0x00; treeTag.BodyPart = 0x00; treeTag.Offset = Vector3.Zero; if (k == 0) { treeTag.Flag = 0x02; } else { var tr_mesh_tree = tr.MeshTreeData.SkipEx((int) trMoveable.MeshTreeIndex + (k - 1) * 4); treeTag.Flag = (ushort) (tr_mesh_tree[0] & 0xFF); treeTag.Offset.X = tr_mesh_tree[1]; treeTag.Offset.Y = tr_mesh_tree[3]; treeTag.Offset.Z = -tr_mesh_tree[2]; } } /* * ================= now, animation loading ======================== */ if (trMoveable.AnimationIndex >= tr.Animations.Length) { // model has no start offset and any animation model.Animations.Resize(1, () => new AnimationFrame()); model.Animations[0].Frames.Resize(1, () => new BoneFrame()); var boneFrame = model.Animations[0].Frames[0]; model.Animations[0].ID = TR_ANIMATION.LaraRun; model.Animations[0].NextAnim = null; model.Animations[0].NextFrame = 0; model.Animations[0].StateChange.Clear(); model.Animations[0].OriginalFrameRate = 1; boneFrame.BoneTags.Resize(model.MeshCount, () => new BoneTag()); boneFrame.Position = Vector3.Zero; boneFrame.Move = Vector3.Zero; boneFrame.V_Horizontal = 0.0f; boneFrame.V_Vertical = 0.0f; boneFrame.Command = 0x00; for (var k = 0; k < boneFrame.BoneTags.Count; k++) { treeTag = model.MeshTree[k]; var boneTag = boneFrame.BoneTags[k]; VMath.Vec4_SetTRRotations(ref boneTag.QRotate, Vector3.Zero); boneTag.Offset = treeTag.Offset; } return; } //Sys.DebugLog(LOG_FILENAME, "model = {0}, anims = {1}", trMoveable.ObjectID, TR_GetNumAnimationsForMoveable(tr, model_num)); model.Animations.Resize(Math.Max(1, TR_GetNumAnimationsForMoveable(tr, model_num)), () => new AnimationFrame()); // the animation count must be >= 1 /* * Ok, let us calculate animations; * there is no difficult: * - first 9 words are bounding box and frame offset coordinates. * - 10's word is a rotations count, must be equal to number of meshes in model. * BUT! only in TR1. In TR2 - TR5 after first 9 words begins next section. * - in the next follows rotation's data. one word - one rotation, if rotation is one-axis (one angle). * two words in 3-axis rotations (3 angles). angles are calculated with bit mask. */ for (var i = 0; i < model.Animations.Count; i++) { var anim = model.Animations[i]; var trAnimation = tr.Animations[trMoveable.AnimationIndex + i]; var frameOffset = trAnimation.FrameOffset / 2; var l_start = 0x09; if (tr.GameVersion.IsAnyOf(TRGame.TR1, TRGame.TR1Demo, TRGame.TR1UnfinishedBusiness)) { l_start = 0x0A; } var frameStep = trAnimation.FrameSize; anim.ID = (TR_ANIMATION) i; anim.OriginalFrameRate = trAnimation.FrameRate; anim.SpeedX = trAnimation.Speed; anim.AccelX = trAnimation.Acceleration; anim.SpeedY = trAnimation.AccelerationLateral; anim.AccelY = trAnimation.SpeedLateral; // TODO: Inverted? anim.AnimCommand = trAnimation.AnimCommand; anim.NumAnimCommands = trAnimation.NumAnimCommands; anim.StateID = (TR_STATE) trAnimation.StateID; anim.Frames.Resize(TR_GetNumFramesForAnimation(tr, trMoveable.AnimationIndex + i), () => new BoneFrame()); //Sys.DebugLog(LOG_FILENAME, "Anim[{0}], {1}", trMoveable.AnimationIndex, TR_GetNumFramesForAnimation(tr, trMoveable.AnimationIndex)); // Parse AnimCommands // Max. amount of AnimCommands is 255, larger numbers are considered as 0. // See http://evpopov.com/dl/TR4format.html#Animations for details. if (anim.NumAnimCommands.IsBetween(0, 255, IB.aEbI)) { // Calculate current animation anim command block offset. Assert(anim.AnimCommand < world.AnimCommands.Length); unsafe { fixed (short* tmp = &world.AnimCommands[(int) anim.AnimCommand]) { var pointer = tmp; for (uint count = 0; count < anim.NumAnimCommands; count++) { var command = *pointer; ++pointer; switch ((TR_ANIMCOMMAND) command) { case TR_ANIMCOMMAND.PlayEffect: case TR_ANIMCOMMAND.PlaySound: // Recalculate absolute frame number to relative. pointer[0] -= (short) trAnimation.FrameStart; pointer += 2; break; case TR_ANIMCOMMAND.SetPosition: // Parse through 3 operands. pointer += 3; break; case TR_ANIMCOMMAND.JumpDistance: // Parse through 2 operands. pointer += 2; break; default: // All other commands have no operands. break; } } } } } if (anim.Frames.Count == 0) { // number of animations must be >= 1, because frame contains base model offset anim.Frames.Resize(1, () => new BoneFrame()); } // let us begin to load animations foreach (var boneFrame in anim.Frames) { boneFrame.BoneTags.Resize(model.MeshCount, () => new BoneTag()); boneFrame.Position = Vector3.Zero; boneFrame.Move = Vector3.Zero; TR_GetBFrameBB_Pos(tr, (int) frameOffset, boneFrame); if (frameOffset >= tr.FrameData.Length) { for (var k = 0; k < boneFrame.BoneTags.Count; k++) { treeTag = model.MeshTree[k]; var boneTag = boneFrame.BoneTags[k]; VMath.Vec4_SetTRRotations(ref boneTag.QRotate, Vector3.Zero); boneTag.Offset = treeTag.Offset; } } else { var l = l_start; ushort temp1, temp2; float ang; for (var k = 0; k < boneFrame.BoneTags.Count; k++) { treeTag = model.MeshTree[k]; var boneTag = boneFrame.BoneTags[k]; VMath.Vec4_SetTRRotations(ref boneTag.QRotate, Vector3.Zero); boneTag.Offset = treeTag.Offset; switch (tr.GameVersion) { case TRGame.TR1: case TRGame.TR1UnfinishedBusiness: case TRGame.TR1Demo: temp2 = tr.FrameData[frameOffset + l]; l++; temp1 = tr.FrameData[frameOffset + l]; l++; VMath.Vec4_SetTRRotations(ref boneTag.QRotate, new Vector3( (temp1 & 0x3ff0) >> 4, -(((temp1 & 0x000f) << 6) | ((temp2 & 0xfc00) >> 10)), temp2 & 0x03ff) * (360.0f / 1024.0f)); break; default: temp1 = tr.FrameData[frameOffset + l]; l++; if (tr.GameVersion >= TRGame.TR4) { ang = (temp1 & 0x0fff) * (360.0f / 4096.0f); } else { ang = (temp1 & 0x03ff) * (360.0f / 1024.0f); } switch (temp1 & 0xc000) { case 0x4000: // x only VMath.Vec4_SetTRRotations(ref boneTag.QRotate, new Vector3(ang, 0, 0)); break; case 0x8000: // y only VMath.Vec4_SetTRRotations(ref boneTag.QRotate, new Vector3(0, 0, -ang)); break; case 0xc000: // z only VMath.Vec4_SetTRRotations(ref boneTag.QRotate, new Vector3(0, ang, 0)); break; default: // all three temp2 = tr.FrameData[frameOffset + l]; VMath.Vec4_SetTRRotations(ref boneTag.QRotate, new Vector3( (temp1 & 0x3ff0) >> 4, -(((temp1 & 0x000f) << 6) | ((temp2 & 0xfc00) >> 10)), temp2 & 0x03ff) * (360.0f / 1024.0f)); l++; break; } break; } } } frameOffset += frameStep; } } // Animations interpolation to 1/30 sec like in original. Needed for correct state change works. model.InterpolateFrames(); // state change's loading if (LOG_ANIM_DISPATCHES) { if (model.Animations.Count > 1) { Sys.DebugLog(LOG_FILENAME, "MODEL[{0}], anims = {1}", model_num, model.Animations.Count); } } for (var i = 0; i < model.Animations.Count; i++) { var anim = model.Animations[i]; anim.StateChange.Clear(); var trAnimation = tr.Animations[trMoveable.AnimationIndex + i]; var animId = (trAnimation.NextAnimation - trMoveable.AnimationIndex) & 0x7fff; // this masks out the sign bit Assert(animId >= 0); if (animId < model.Animations.Count) { anim.NextAnim = model.Animations[animId]; anim.NextFrame = Math.Max(0, (trAnimation.NextFrame - tr.Animations[trAnimation.NextAnimation].FrameStart) % anim.NextAnim.Frames.Count); if (LOG_ANIM_DISPATCHES) { Sys.DebugLog(LOG_FILENAME, "ANIM[{0}], next_anim = {1}, next_frame = {2}", i, (int) anim.NextAnim.ID, anim.NextFrame); } } else { anim.NextAnim = null; anim.NextFrame = 0; } anim.StateChange.Clear(); // TODO: Needed? if (trAnimation.NumStateChanges > 0 && model.Animations.Count > 1) { if (LOG_ANIM_DISPATCHES) { Sys.DebugLog(LOG_FILENAME, "ANIM[{0}], next_anim = {1}, next_frame = {2}", i, anim.NextAnim == null ? -1 : (int) anim.NextAnim.ID, anim.NextFrame); } anim.StateChange.Resize(trAnimation.NumStateChanges, () => new StateChange()); for (var j = 0; j < trAnimation.NumStateChanges; j++) { var schP = anim.StateChange[j]; var trSch = tr.StateChanges[j + trAnimation.StateChangeOffset]; schP.ID = (TR_STATE) trSch.StateID; schP.AnimDispatch.Clear(); for (var l = 0; l < trSch.NumAnimDispatches; l++) { var trAdisp = tr.AnimDispatches[trSch.AnimDispatch + l]; var nextAnim = trAdisp.NextAnimation & 0x7fff; var nextAnimInd = nextAnim - (trMoveable.AnimationIndex & 0x7fff); if (nextAnimInd < model.Animations.Count) { var adsp = new AnimDispatch(); var nextFramesCount = model.Animations[nextAnim - trMoveable.AnimationIndex].Frames.Count; var nextFrame = trAdisp.NextFrame - tr.Animations[nextAnim].FrameStart; var low = trAdisp.Low - trAnimation.FrameStart; var high = trAdisp.High - trAnimation.FrameStart; adsp.FrameLow = (ushort) (low % anim.Frames.Count); adsp.FrameHigh = (ushort) ((high - 1) % anim.Frames.Count); adsp.NextAnim = (TR_ANIMATION) (nextAnim - trMoveable.AnimationIndex); adsp.NextFrame = (ushort) (nextFrame % nextFramesCount); schP.AnimDispatch.Add(adsp); if (LOG_ANIM_DISPATCHES) { Sys.DebugLog(LOG_FILENAME, "anim_disp[{0}], frames.size() = {1}: interval[{2}.. {3}], next_anim = {4}, next_frame = {5}", l, anim.Frames.Count, adsp.FrameLow, adsp.FrameHigh, (int) adsp.NextAnim, adsp.NextFrame); } } } } } } GenerateAnimCommandsTransform(model); }
public static void TR_GenSkeletalModels(World world, Level tr) { world.SkeletalModels.Resize(tr.Moveables.Length); for (var i = 0; i < tr.Moveables.Length; i++) { var tr_moveable = tr.Moveables[i]; var smodel = new SkeletalModel(); smodel.ID = tr_moveable.ObjectID; smodel.MeshCount = tr_moveable.NumMeshes; TR_GenSkeletalModel(world, i, smodel, tr); smodel.FillTransparency(); world.SkeletalModels[i] = smodel; } }
private void createHairMesh(SkeletalModel model) { Mesh = new BaseMesh(); Mesh.ElementsPerTexture.Resize(EngineWorld.Textures.Count); var totalElements = 0; // Gather size information for (var i = 0; i < model.MeshCount; i++) { var original = model.MeshTree[i].MeshBase; Mesh.TexturePageCount = Math.Max(Mesh.TexturePageCount, original.TexturePageCount); for (var j = 0; j < original.TexturePageCount; j++) { Mesh.ElementsPerTexture[j] += original.ElementsPerTexture[j]; totalElements += (int) original.ElementsPerTexture[j]; } } // Create arrays Mesh.Elements.Resize(totalElements); // - with matrix index information Mesh.MatrixIndices.Resize(Mesh.Vertices.Count, () => new BaseMesh.MatrixIndex()); // Copy information var elementsStartPerTexture = new List<uint>(); elementsStartPerTexture.Resize((int)Mesh.TexturePageCount); Mesh.Vertices.Clear(); for (var i = 0; i < model.MeshCount; i++) { var original = model.MeshTree[i].MeshBase; // Copy vertices var verticesStart = Mesh.Vertices.Count; // TODO: Wut... size == 0 (cf. L328) Mesh.Vertices.AddRange(original.Vertices); // Copy elements var originalElementsStart = 0; for (var page = 0; page < original.TexturePageCount; page++) { if (original.ElementsPerTexture[page] == 0) continue; Assert(originalElementsStart < original.Elements.Count); Assert(originalElementsStart + original.ElementsPerTexture[page] <= original.Elements.Count); Assert(elementsStartPerTexture[page] < Mesh.Elements.Count); Assert(elementsStartPerTexture[page] + original.ElementsPerTexture[page] <= Mesh.Elements.Count); Helper.ListCopy(original.Elements, originalElementsStart, Mesh.Elements, (int) elementsStartPerTexture[page], (int) original.ElementsPerTexture[page]); for (var j = 0; j < original.ElementsPerTexture[page]; j++) { Mesh.Elements[(int) elementsStartPerTexture[page]] = (uint) (verticesStart + original.Elements[originalElementsStart]); originalElementsStart++; elementsStartPerTexture[page]++; } } // Apply total offset from parent. // The resulting mesh will have all the hair in default position // (i.e. as one big rope). The shader and matrix then transform it // correctly. Elements[i].Position = model.MeshTree[i].Offset; if(i > 0) { // TODO: This assumes the parent is always the preceding mesh. // True for hair, obviously wrong for everything else. Can stay // here, but must go when we start generalizing the whole thing. Elements[i].Position += Elements[i - 1].Position; } // And create vertex data (including matrix indices) for (var j = 0; j < original.Vertices.Count; j++) { Mesh.MatrixIndices.Add(new BaseMesh.MatrixIndex()); Assert(Mesh.MatrixIndices.Count > verticesStart + j); if (original.Vertices[j].Position[1] <= 0) { Mesh.MatrixIndices[verticesStart + j].I = (sbyte) i; Mesh.MatrixIndices[verticesStart + j].J = (sbyte) (i + 1); } else { Mesh.MatrixIndices[verticesStart + j].I = (sbyte) (i + 1); Mesh.MatrixIndices[verticesStart + j].J = Math.Min((sbyte) (i + 2), (sbyte) model.MeshCount); } // Now move all the hair vertices Mesh.Vertices[verticesStart + j].Position += Elements[i].Position; // If the normal isn't fully in y direction, cancel its y component // This is perhaps a bit dubious. if (Mesh.Vertices[verticesStart + j].Normal.X != 0 || Mesh.Vertices[verticesStart + j].Normal.Z != 0) { Mesh.Vertices[verticesStart + j].Normal.Y = 0; Mesh.Vertices[verticesStart + j].Normal.Normalize(); } } } Mesh.GenVBO(Renderer); }
public void Prepare() { ID = 0; Name = ""; Type = 0; Meshes = new List<BaseMesh>(); Sprites = new List<Sprite>(); Rooms = new List<Room>(); FlipData = new List<FlipInfo>(); Textures = new List<uint>(); EntityTree = new Dictionary<uint, Entity>(); ItemsTree = new Dictionary<uint, BaseItem>(); Character = null; #if !NO_AUDIO AudioSources = new List<AudioSource>(); AudioBuffers = new uint[0]; AudioEffects = new List<AudioEffect>(); AudioEmitters = new List<AudioEmitter>(); AudioMap = new List<short>(); StreamTracks = new List<StreamTrack>(); StreamTrackMap = new List<byte>(); #endif AnimSequences = new List<AnimSeq>(); RoomBoxes = new List<RoomBox>(); CamerasSinks = new List<StatCameraSink>(); SkeletalModels = new List<SkeletalModel>(); SkyBox = null; AnimCommands = new short[0]; }
public void FromModel(SkeletalModel model) { HasSkin = false; BBMin = Vector3.Zero; BBMax = Vector3.Zero; Centre = Vector3.Zero; Position = Vector3.Zero; Animations = new SSAnimation(); Animations.Model = model; BoneTags.Resize(model.MeshCount, () => new SSBoneTag()); var stack = 0; var parents = new List<SSBoneTag>(); parents.Resize(BoneTags.Count, () => new SSBoneTag()); parents[0] = null; BoneTags[0].Parent = null; for (ushort i = 0; i < BoneTags.Count; i++) { BoneTags[i].Index = i; BoneTags[i].MeshBase = model.MeshTree[i].MeshBase; BoneTags[i].MeshSkin = model.MeshTree[i].MeshSkin; if (BoneTags[i].MeshSkin != null) HasSkin = true; BoneTags[i].MeshSlot = null; BoneTags[i].BodyPart = model.MeshTree[i].BodyPart; BoneTags[i].Offset = model.MeshTree[i].Offset; BoneTags[i].QRotate = new Quaternion(0, 0, 0, 0); BoneTags[i].Transform.SetIdentity(); BoneTags[i].FullTransform.SetIdentity(); if (i > 0) { BoneTags[i].Parent = BoneTags[i - 1]; if (model.MeshTree[i].Flag.HasFlagUns(0x01)) // POP { if (stack > 0) { BoneTags[i].Parent = parents[stack]; stack--; } } if (model.MeshTree[i].Flag.HasFlagUns(0x02)) // PUSH { if (stack + 1 < (short) model.MeshCount) { stack++; parents[stack] = BoneTags[i].Parent; } } } } }