using System.Numerics; using AcDream.Core.World; using DatReaderWriter.DBObjs; using DatReaderWriter.Enums; using DatReaderWriter.Types; namespace AcDream.Core.Meshing; public static class SetupMesh { /// /// Flatten a Setup into a list of (GfxObjId, PartTransform) refs. /// Uses the default placement frame and DefaultScale per part. /// Does NOT walk ParentIndex — each part's transform is local to the setup root. /// This is simplification for Phase 2; complex hierarchical rigs are Phase 3. /// public static IReadOnlyList Flatten(Setup setup, AnimationFrame? motionFrameOverride = null) { // Pose source priority: // 1. motionFrameOverride — caller has resolved an idle/animation // frame from the entity's MotionTable (Phase 6). This is the // best source for creatures and characters because their // Setup.PlacementFrames don't define an upright idle pose. // 2. Setup.PlacementFrames[Resting] — used by static objects // that have a Resting frame defined (e.g. signs, doors). // 3. Setup.PlacementFrames[Default] — fallback for everything // else (most scenery), which is the only frame those setups // define and renders correctly. // // Without an override, creatures used to render in their Default // pose (T-pose-ish or aggressive crouch) because their MotionTable // wasn't consulted. The Phase 6 MotionResolver provides the override // by walking Setup.DefaultMotionTable → MotionTable.Cycles → // Animation.PartFrames[LowFrame]. AnimationFrame? defaultAnim = motionFrameOverride; if (defaultAnim is null && setup.PlacementFrames.TryGetValue(Placement.Resting, out var resting)) defaultAnim = resting; if (defaultAnim is null && setup.PlacementFrames.TryGetValue(Placement.Default, out var af)) defaultAnim = af; // Last resort: use the first available placement frame (matches ACME's // StaticObjectManager.GetDefaultPlacementFrame third fallback). Handles // rare Setups that define only an unusual placement frame key. if (defaultAnim is null) { foreach (var kvp in setup.PlacementFrames) { defaultAnim = kvp.Value; break; } } var result = new List(setup.Parts.Count); for (int i = 0; i < setup.Parts.Count; i++) { uint gfxObjId = (uint)setup.Parts[i]; Frame frame; if (defaultAnim is not null && i < defaultAnim.Frames.Count) frame = defaultAnim.Frames[i]; else frame = new Frame { Origin = Vector3.Zero, Orientation = Quaternion.Identity }; Vector3 scale = i < setup.DefaultScale.Count ? setup.DefaultScale[i] : Vector3.One; var transform = Matrix4x4.CreateScale(scale) * Matrix4x4.CreateFromQuaternion(frame.Orientation) * Matrix4x4.CreateTranslation(frame.Origin); result.Add(new MeshRef(gfxObjId, transform)); } return result; } }