Add CSC (Composite Scene) editor#371
Conversation
- WpfMouse: a missed hit-test mid-drag no longer stops position tracking before the button is released, so fast mouse movement during a capture drag doesn't freeze the cursor. - ComplexMeshLoader: nested <VARIANT_MESH> children under a <SLOT> were losing their attach_point name (the recursive call passed the outer attachmentPointName through instead of slot.AttachmentPoint). - SkeletonBoneAnimationResolver: added a bind-pose fallback when no animation frame is available and a bounds check against BoneCount, so attach_point/rigid-bone meshes don't collapse to the model origin. - Pin SQLitePCLRaw.bundle_e_sqlite3 2.1.12 in the three TreatWarningsAsErrors projects that pull in a vulnerable transitive version (NU1903 audit advisory). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a full editor for Total War .csc files: an ESF envelope reader/writer (Shared/GameFiles/Esf, ported from RPFM's ESF format handling), CSC-specific grammar detectors for ROOT/ELEMENT/ COMPOSITE_SCENE (Shared/GameFiles/CSC), and the editor itself (Editors/CscEditor) - scene graph building, skeletal animation playback/blending/splicing across overlapping bindings, a custom gizmo, a keyframe curve editor, and a look-through-camera porthole preview. Supporting engine changes needed by the editor: - Camera: ViewMatrixOverride/ProjectionMatrixOverride for looking through a scene-defined camera. - RenderEngineComponent/RenderTargetHelper: HideGrid, SquareRenderSize and an alpha-preserving LastFrame for the porthole preview. - GameSkeleton.CreateFromAnimationFile: builds a skeleton directly from an animation's own bone table for "ad-hoc" skeletons that have no dedicated skeleton file (e.g. building-destruction rigs) - also wired into SceneObjectEditor.SetAnimation for every editor. - AnimationPlayer.SetManualFrame: lets external code inject an already-sampled frame, used for cross-fading overlapping clips. - Rmv2MeshNode: corrected bone-attach and pivot composition order, and a new AttachmentOuterWorld slot so CSC's flat scene graph (which must push a leaf's full world into ModelMatrix for picking) and Kitbasher's nested scenegraph can share the same attach-bone resolver without one clobbering the other. 22 new unit tests (ESF round-trip, channel evaluation, scene round-trip with hierarchy/keyframes, nested-element handling). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
donkeyProgramming
left a comment
There was a problem hiding this comment.
Well done!
Have a few questions/changes for the "core" part of the application.
| <TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> |
There was a problem hiding this comment.
Why is this added and why is it needed?
There was a problem hiding this comment.
Okay, this definitely is because of that mistaken BMD Editor branch editing (it was way out of date), and then blindly brought over to this new, up to date branch. It was suppressing some security warning (SQLitePCLRaw.lib.e_sqlite3). And your already suppressing it in some other way. So this edit was pointless.
| } | ||
| } | ||
| }); | ||
| WireAttachmentResolvers(sceneObject); |
There was a problem hiding this comment.
Can you add a set of unit tests to verify that this works?
Attachments are a recurring point for bugs, so lets make sure we dont jump between different versions without having a chance to verify how it should work,
There was a problem hiding this comment.
Added a new Test.Editors.Shared.Core project — 10 tests covering bone-name match/no-match, no-skeleton, rigid-bone matrix-override in/out of range, nested meshes under a group, and re-wiring after a skeleton swap.
| /// <summary>Directly overrides the sampled frame for this tick (e.g. cross-fade blending | ||
| /// across multiple clips) - bypasses SetAnimation/CurrentFrame's normal single-clip | ||
| /// sampling. Overwritten again on the next call that touches CurrentFrame/Refresh.</summary> | ||
| public void SetManualFrame(AnimationFrame frame) => _currentAnimFrame = frame; |
There was a problem hiding this comment.
So this is needed because while scrubbing on the timeline its possible to set the frame to say 1.5, a mix between frame 1 and 2?
Instead of adding a hack/workaround, change the int CurrentFrame to support this behavior by changing it to a float/double.
There was a problem hiding this comment.
CSC never actually touches CurrentFrame — it drives multiple overlapping clip bindings (cross-fade blends, bone-splice overrides) through its own fractional sampler, and SetManualFrame is how it injects that externally-composed multi-clip result. A float CurrentFrame wouldn't let us drop SetManualFrame, since what's being injected is never "this one player's own clip at time T" — happy to make CurrentFrame a float separately if useful elsewhere, but it doesn't fix this.
| /// <summary>When true, skips the editing-grid render bucket (see Render3DObjects) - for | ||
| /// consumers that want the normal-editing grid hidden, e.g. a "look through a scene camera" | ||
| /// preview where nothing but the in-game content should show.</summary> | ||
| public bool HideGrid { get; set; } |
There was a problem hiding this comment.
This can already be controlled by RenderSettingsModel
There was a problem hiding this comment.
Good catch — replaced with the existing GridComponent.ShowGrid (same thing RenderSettingsModel already drives). HideGrid is gone.
| /// preview where nothing but the in-game content should show.</summary> | ||
| public bool HideGrid { get; set; } | ||
|
|
||
| /// <summary>When set, renders at this fixed square pixel size instead of the live (and |
There was a problem hiding this comment.
Please remove the AI generated comments and replace them with a better description.
For the "Core" parts of the application its important to keep things clean and understandable.
There was a problem hiding this comment.
Cleaned up.
Beep boop.
| var spriteBatch = CommonSpriteBatch; | ||
| var screenWidth = device.Viewport.Width; | ||
| var screenHeight = device.Viewport.Height; | ||
| var screenWidth = SquareRenderSize ?? device.Viewport.Width; |
There was a problem hiding this comment.
Why is the SquareRenderSize needed? Is it possible to just override the viewport instead?
There was a problem hiding this comment.
Replaced with SquareViewport — restricts the rasterization viewport instead of resizing/reallocating the render targets, so it no longer reallocates GPU resources every time look-through toggles.
(Human ChaosRobie here, if your asking a general question about square 3d view: It's needed for the look-through-camera 3d porthole previewer. It prevents the 3d view from being resized into weirdness)
| (ButtonState)e.LeftButton, (ButtonState)e.MiddleButton, (ButtonState)e.RightButton, (ButtonState)e.XButton1, | ||
| (ButtonState)e.XButton2); | ||
| // only release if LeftMouse is up | ||
| // Once capture is held (left-button drags only - see CaptureMouse() below), |
There was a problem hiding this comment.
Have you verified this behavior across multiple use cases and editors?
I think I have already reverted 4 AI generated fixes for this, as they all have caused more issues then they actually solved.
There was a problem hiding this comment.
This was added when I was editing my BMD Editor branch, which was very out of date (my mistake). It was legitimately an issue from then, but it's likely it was just blindly brought over when I made the CSC Editor branch.
There was a problem hiding this comment.
Still seems to be an issue.
https://github.com/user-attachments/assets/696744e9-a760-486f-bb50-510e9ed1bf92
This is with it reverted. I'm alt click-dragging like a mad man, trying to orbit the camera and there's only a few frames of orbiting.
There was a problem hiding this comment.
Okay, I pushed Claude for a real explanation/solution. And it seems to have found it (I helped test!). CSC Editor was special since it had an overlay sharing the 3d view's frame/cell thing.
There was a problem hiding this comment.
Claude's official response:
VisualTreeHelper.HitTest ignores visibility, so CSC's hidden porthole overlay was still registering as a "hit" and breaking mouse capture — only happened in CSC because it's the only editor with that overlay. Confirmed it live with some temp logging rather than just guessing.
Fix is just swapping those two hit-tests for InputHitTest, which does respect visibility. Ditched the old position-tracking workaround entirely — everything else matches master now. Different bug than the ones you'd reverted before, this one's for real.
| <TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> |
There was a problem hiding this comment.
SQLitePCLRaw.lib.e_sqlite3 supression thing, unnecessary. Reverted.
|
|
||
|
|
||
| namespace Shared.GameFormats.Csc; | ||
|
|
There was a problem hiding this comment.
Can you add some unit tests for this?
Just a few tests for saving and loading that verifies that it works.
There was a problem hiding this comment.
Added 8 isolated tests for DetectGroups/DetectEntrySection/DetectFooter directly, including malformed-input cases — complements the existing round-trip test that already exercises save/load through the full scene pipeline.
| <PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.8" /> | ||
| <PackageReference Include="LZMA-SDK" Version="22.1.1" /> | ||
| <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.7" /> | ||
| <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.12" /> |
There was a problem hiding this comment.
SQLitePCLRaw.lib.e_sqlite3 supression thing, unnecessary. Reverted.
- Replace RenderEngineComponent.HideGrid/SquareRenderSize with GridComponent.ShowGrid and a SquareViewport rasterization-viewport restriction, avoiding the render-target reallocation churn of the old approach - Clean up AI-sounding doc comments in RenderEngineComponent.cs - Fix WpfMouse.cs's real bug: VisualTreeHelper.HitTest ignores Visibility/IsHitTestVisible and was matching CSC's collapsed porthole overlay instead of the 3D view, causing near-total loss of mouse tracking during a drag; switched both hit-test call sites to UIElement.InputHitTest, which respects those properties. The previous "keep tracking position on a hit-test miss" workaround is no longer needed and has been reverted, so this file now matches master except for the InputHitTest swap - Add unit tests for SceneObjectEditor.WireAttachmentResolvers (new Test.Editors.Shared.Core project) and for CompositeSceneDetector's grammar parsing (Test.CscEditor) - Remove the SQLitePCLRaw.bundle_e_sqlite3 pin from the three csproj files that had it - turned out to be unnecessary, since the one advisory it addressed is already suppressed solution-wide via Directory.Build.props Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Embedded Composite Scene (ROOT_REF) contents are now hidden as a whole while their host element is outside its own Begin/End window, cascading through nested root-refs - unlike a normal element's own children, which never inherit a parent's aliveness - Toolbar gizmo buttons (Move/Rotate/Scale/off) now use the same icons as Kitbasher instead of text labels - Fixed ComputeSubSceneTime: a negative ELEMENT_PERIOD speed on a ROOT_REF host now plays the referenced scene in reverse (looping continuously) instead of permanently freezing at frame 0 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Summary
Test plan