11 Commits
Author SHA1 Message Date
zwitschi 0d7ceae474 docs(memory): record the vehicle settle fix and the failed root-bone experiment 2026-11-10 21:33:31 +01:00
zwitschi 319096d0d9 fix(vehicle): settle the chassis on the road (float 33 m -> 6 cm)
Two root causes, both in how the chassis box was authored:

1. The box centre was -Extent.Z*0.25, below the root bone, so the box sat under the
   ground and the suspension pushed the whole vehicle up. The skeletal mesh imports
   ROLLED (height along Y, width along Z) while the armature is unrolled, so the box is
   now built with swapped axes (length from mesh X, width from mesh Z, height from mesh
   Y) and centred at the rolled mesh's mid-height (Bounds.Origin.Y). The vehicle now
   settles at Z 6 cm instead of floating at 33 m.

2. A left-over diagnostic stopped the movement component at 2.5 s whenever the pawn
   ticked, which cut the drive test (throttle runs 1-4 s). It is now gated on the vehicle
   actually having zero wheels.

Remaining: the drive test still reports ~0 forward speed, so the throttle does not yet
move the car - the wheels rest ~2 cm above the ground because the suspension rest
position is measured against the root bone while the box sits at the centre of mass.
2026-11-10 21:33:14 +01:00
zwitschi 84291a5270 fix(vehicle): raise the chassis box above the ground (float 33m -> 7.8m)
Box.Center was -Extent.Z*0.25 (below the root bone); the box then sat under the ground
and the suspension pushed the chassis up. Raising it to +Extent.Z*0.35 cuts the float
from ~33 m to 7.8 m, confirming the direction. Still not settled - the box centre and/or
the root bone (grounded at Z 0 by normalise_scale) need to move to the car's centre of
mass.
2026-11-10 20:59:53 +01:00
zwitschi 63ba9d91ce feat(vehicle): build the skeletal mesh from the hot hatch
vehicle_from_template.py now authors the physics mesh from the real hot hatch: apply
the mirror/nodes modifiers, derive the 4 wheel centres, add a root + 4 wheel bones,
weight and export vehicle_skeletal.fbx. A rotation orients the car so it imports
unrolled (length +X, width +Y, height +Z); wheel bones land at front +X, rear -X,
left/right +/-Y. The vehicle still floats (rises to 33 m) - recorded in MEMORY.
2026-11-10 20:47:54 +01:00
zwitschi d8cbb0c95b docs(vehicle): the visual and physics bodies are different vehicles
SM_Vehicle_Body (hot hatch) is 150x400x122 (length along Y); SK_Vehicle_Sedan is
476x228x144 (length along X). They are built by different scripts and do not share
a forward axis - the likely real cause of the reverse drive. MaxTorque sign was
also a no-op. Next: reconcile the two meshes into one asset with one forward axis.
2026-11-10 19:58:51 +01:00
zwitschi dd36d399ad chore: add skills 2026-11-10 19:57:56 +01:00
zwitschi 911a7059ae docs(vehicle): revert the axis fix; the reverse is in the Chaos solver
The axis_forward:X / Blender-Z axle fix changed the reference pose but not the drive
(-2737 cm/s), and axis_forward:X also alters the shared character preset. Revert it.
The Chaos solver drives the wheel about the vehicle's Y (not the bone), and the
drive force is positive yet the car moves backward - the inversion is in the engine
torque / drive-force sign. Next: GetEngineTorque() sign or a SetupVehicle override.
2026-11-10 19:45:17 +01:00
zwitschi ecdd8208db fix(vehicle): author wheel axles horizontally; record reverse is not the axle
Wheel axles were vertical in Unreal (the FBX armature conversion rolled them 90 deg
about X). Fix: axis_forward:X in the character preset and a Blender-Z wheel tail so the
axle lands on Unreal Y. This is correct (reference pose FL roll 180 / FR roll 0) but
does not fix the reverse: -drivetest still -2737 cm/s, and FinalRatio sign is a no-op.
The remaining cause is the Chaos wheel spin-axis convention or engine torque sign.
2026-11-10 19:27:36 +01:00
zwitschi a2f8ddccf1 docs(vehicle): record the FBX bakes a -90 deg armature roll
The export rotates the armature -90 deg about X (wheel axle on Z) while leaving the
mesh unrotated. Head/tail, axis_forward:X, and armature pre-roll all failed to change
the imported bones. Next: post-import skeleton fix or bake_space_transform.
2026-11-10 19:06:11 +01:00
zwitschi 3d4e830660 docs(vehicle): the reverse is a 90-degree armature roll, not the axle sign
Bone reference poses show root roll 90 and wheel bones rolled +/-90, with wheel
centres at Z +/-0.81 instead of Y. The mesh vertices are unrotated, so the FBX
axis_forward:-Y conversion rotates the armature but not the geometry, leaving the
wheel axle on a rotated axis. Fix is in the bone authoring, verified by the
reference-pose probe.
2026-11-10 18:52:24 +01:00
zwitschi c3aacabd46 test(vehicle): add -reversetest and record the reverse is wheel-spin, not throttle
-reversetest holds negative throttle; forward throttle drives -224.9 km/h and reverse
throttle -99.5 km/h, both backward, so the throttle sign is not the cause - the wheel
spin is inverted relative to the engine torque. Mesh orientation is fine (nose +X).
Next: check the imported wheel-bone reference pose (axle may land on X/Z, not Y).
2026-11-10 18:48:27 +01:00
10 changed files with 412 additions and 20 deletions

No files matched your search

+55 -4
View File
@@ -185,16 +185,49 @@ The project-specific pack, `.agents/skills/blender-skills/blender-director/` —
Do not copy the common packs into the repository: a second copy under a different root is a name collision, and Pi keeps the first one it finds and warns on every start. Add a pack to `~/.pi/agent/skills/<pack>/SKILL.md` (one level down, flat) and it is picked up everywhere; `references/` sits beside them at `~/.pi/agent/skills/references/`.
**All agents must consult the relevant skill packs before starting a task in that discipline.** Each patch section in `TODO.md` lists its suggested skills; the general mapping is:
**Entry point:** `blender-director` — the orchestrator picks the specialist packs in pipeline order and matches a reference photograph before detail work. Start any multi-step Blender task there.
- **Modeling / procedural assets** — `blender-modeler`, `procedural-modeling`, `geometry-nodes`, `hard-surface`.
- **Façades and architecture** — `archviz`, `historical-worlds` (1920s Hollywood Blvd direction), `realistic-style`.
**Pack index.** Every pack available to this project, grouped by discipline. The 93 common packs plus the project's `blender-director`; `unity-export` and `godot-export` are installed for other projects and unused here, since the project ships Unreal only.
| Group | Packs |
| --- | --- |
| Pipeline entry point | `blender-director` (project pack) |
| Core modelling | `blender-modeler`, `hard-surface`, `prop-artist`, `vehicle-artist`, `character-artist`, `creature-artist`, `sculpting`, `retopology`, `geometry-nodes`, `procedural-modeling` |
| Environment and architecture | `environment-artist`, `archviz`, `set-dressing`, `vegetation-artist`, `scene-assembly`, `collision-proxy` |
| Characters and animation | `character-archetypes`, `rigging`, `animation`, `cloth-sim`, `hair-groom` |
| Materials, light and render | `materials`, `texture-workflow`, `lookdev`, `lighting`, `rendering`, `compositing`, `camera-cinematography` |
| UVs, export and optimisation | `uv-workflow`, `export-pipeline`, `unreal-export`, `asset-optimization`, `lod-pipeline` |
| Physics and VFX | `physics-sim`, `vfx-fx` |
| Review and QA | `qa-review`, plus the `references/` notes below |
| Project direction | `realistic-style`, `historical-worlds` |
| World packs | `apocalypse-worlds`, `biome-worlds`, `fantasy-worlds`, `sci-fi-punk-worlds` |
| Genre art direction | `genre-action-combat`, `genre-card-party-idle`, `genre-metroidvania-roguelike`, `genre-narrative-vn`, `genre-open-world-sandbox`, `genre-puzzle-platformer`, `genre-racing-sports`, `genre-rpg`, `genre-shooter`, `genre-soulslike`, `genre-stealth`, `genre-strategy-sim`, `genre-survival` |
| Mood art direction | `brutalist-mood`, `cozy-wholesome-mood`, `dark-gritty-mood`, `dream-surreal-mood`, `neon-retrofuturism`, `noir-style` |
| Style art direction | `analog-found-footage-horror`, `anime-style`, `body-horror-style`, `cartoon-style`, `chibi-style`, `comic-book-style`, `cosmic-eldritch-horror`, `dream-weirdcore-style`, `folk-horror-style`, `frutiger-aero-style`, `hand-painted-style`, `hd-2d-style`, `horror-style`, `indie-horror-aesthetics`, `isometric-style`, `liminal-space-style`, `lowpoly-style`, `manga-style`, `mascot-puppet-horror`, `minimalist-style`, `painterly-style`, `pixel-art-style`, `psx-horror-style`, `retro-16bit-style`, `retro-8bit-style`, `stop-motion-craft-style`, `stylized-style`, `vector-style`, `visual-console-eras`, `voxel-style` |
| Other engines (unused) | `unity-export`, `godot-export` |
**All agents must consult the relevant skill packs before starting a task in that discipline.** Each task section in `TODO.md` names its suggested skills; the general mapping is:
- **Modeling / procedural assets** — `blender-modeler`, `procedural-modeling`, `geometry-nodes`, `hard-surface`, `prop-artist`, `sculpting`, `retopology`.
- **Vehicles** — `vehicle-artist` with `hard-surface`, `collision-proxy`, `lod-pipeline` (the `0.3.0` car).
- **Façades and architecture** — `archviz`, `historical-worlds` (1920s Hollywood Blvd direction), `realistic-style`, `environment-artist`.
- **Characters and animation** — `character-artist`, `rigging`, `animation`, `cloth-sim`, `hair-groom` (the MPFB `AGigLineNPCPerson` prefab; `character-archetypes` for crowd variety).
- **Materials and textures** — `materials`, `texture-workflow`, `lookdev`.
- **UVs and export** — `uv-workflow`, `export-pipeline`, `unreal-export` (verify against the `ue_metric_v1` / `ue_character_v1` presets in `Tools/`).
- **UVs and export** — `uv-workflow`, `export-pipeline`, `unreal-export` (verify against the `ue_metric_v1` / `ue_character_v1` presets in `Tools/`), `asset-optimization`, `lod-pipeline`.
- **Collision** — `collision-proxy` (UCX/UHX naming, hulls, simple colliders).
- **Scene assembly / optimization** — `scene-assembly`, `asset-optimization`, `lod-pipeline`.
- **Lighting and rendering** — `lighting`, `rendering`, `lookdev`, `compositing`, `camera-cinematography`.
- **Blender-side physics and VFX** — `physics-sim`, `vfx-fx`.
- **Review and QA** — `qa-review`, `references` (reference-image-match and visual-match checklists), `camera-cinematography` (matching reference camera angles).
**Reference notes.** `~/.pi/agent/skills/references/` sits beside the packs and holds the checklists they cite:
- `asset-pipeline.md` — the stage order every asset follows.
- `reference-image-match.md` and `reference-analysis-template.md` — match the camera and write the analysis before micro-detail when a photograph drives the asset.
- `visual-match-checklist.md` and `validation-checklist.md` — the two gates before an asset is called done.
- `naming-conventions.md` and `polycount-budgets.md` — names and budgets to hold to.
- `mcp-integration.md` and `mcp-tools.md` — the packs' MCP tooling, **not used here** (see the caveat below).
**Caveat:** the skill packs assume MCP-driven interactive Blender. This project runs Blender headless via scripts in `Tools/` — use the skills as method guides (checklists, naming conventions, proportions, validation steps), not for their MCP tooling. The headless scripts remain the execution path.
## Unreal Engine skills
@@ -224,6 +257,24 @@ The general mapping to this project's work:
One router skill, `cpp` (MIT, [DiamonDinoia/skill-cpp](https://github.com/DiamonDinoia/skill-cpp) `f2617fb`), lives at `.agents/skills/cpp/` and covers general C++11 through C++23 against the C++ Core Guidelines. It detects the project language standard from the build system (**C++20** for this project, from the UBT compile flags), then loads the matching standard reference plus the topic references the task touches. Load it for any C++ work that is not Unreal-specific; the UE skills supersede it for engine API questions.
## Skill roots and provenance
Every skill pack is agent instruction, never game code or an add-on, and none is committed: `/.agents/skills/` is gitignored. The runtime discovers packs from these roots:
| Root | Packs | Source |
| --- | --- | --- |
| `~/.pi/agent/skills/<pack>/SKILL.md` | 93 Blender packs, flat, one level down | [blender-skills](https://github.com/arjun988/blender-skills) 1.3.0, MIT |
| `~/.pi/agent/skills/references/` | shared notes beside the packs, not a pack | same |
| `.agents/skills/blender-skills/blender-director/` | 1 project orchestrator | project-specific |
| `.agents/skills/ue-*/` | 31 Unreal Engine 5.8 C++ packs | [quodsoler/unreal-engine-skills](https://github.com/quodsoler/unreal-engine-skills) `f3742d7`, MIT |
| `.agents/skills/cpp/` | 1 C++ router | [DiamonDinoia/skill-cpp](https://github.com/DiamonDinoia/skill-cpp) `f2617fb`, MIT |
| `~/.agents/skills/` | user-level packs shared with other projects | local |
- `.agents/skills/skills.manifest.json` records every installed source, commit, target and local patch, with `LICENSE-unreal-engine-skills.txt` and `LICENSE-skill-cpp.txt` beside it. Keep the manifest current when a pack is installed, upgraded or removed.
- The upstream Blender clone sits at `~/.agents/skills/blender-skills/` (`git remote https://github.com/arjun988/blender-skills.git`, 94 packs under `.claude/skills/`). `~/.pi/agent/skills/` is the installed copy the runtime actually reads and holds 93 of them; `blender-director` is kept project-local because it names this project's tools and stages.
- Because the packs are gitignored, a fresh checkout has none of them: every task must stay doable from the scripts in `Tools/`, `Geo/tools/` and `Art/blender/`, with the packs only guiding method.
- User-level packs: `de-ai-writing` strips AI-writing tells from `Docs/`, `CHANGELOG.md` and `MEMORY.md` prose; `hf-cli` (Hugging Face Hub CLI) and `reverse-engineer-anything` (REA for shipped binaries and packaged builds) are not part of this pipeline and are loaded only when the task is actually about them.
## Licensing & Distribution
- Publish project-owned code under a chosen open-source license.
Binary file not shown.
+13 -9
View File
@@ -318,7 +318,8 @@ void AGigLineVehicle::Tick(float DeltaSeconds)
{
if (DriveElapsed > 1.0f && DriveElapsed < 4.0f)
{
Movement->SetThrottleInput(1.0f);
const float Throttle = FParse::Param(FCommandLine::Get(), TEXT("reversetest")) ? -1.0f : 1.0f;
Movement->SetThrottleInput(Throttle);
}
else if (DriveElapsed >= 4.0f && !bDriveReported)
{
@@ -444,19 +445,22 @@ void AGigLineVehicle::Tick(float DeltaSeconds)
// If the Chaos vehicle never came into existence (no wheels), its movement component has no
// valid state to drive, and continuing to tick it writes whatever is in that empty state back
// onto the actor - which is the only explanation that fits a still body and a moving actor.
// Only stop in that case: with real wheels the mover must keep ticking to drive, and stopping
// it at 2.5 s was what killed the drive test (throttle runs 1-4 s).
if (!bMoverStopped && SettleElapsed > 2.5f)
{
bMoverStopped = true;
if (const UChaosWheeledVehicleMovementComponent* WheelComp =
Cast<UChaosWheeledVehicleMovementComponent>(GetVehicleMovementComponent()))
const UChaosWheeledVehicleMovementComponent* WheelComp =
Cast<UChaosWheeledVehicleMovementComponent>(GetVehicleMovementComponent());
if (!WheelComp || WheelComp->Wheels.Num() == 0)
{
bMoverStopped = true;
UE_LOG(LogTemp, Warning,
TEXT("GigLine vehicle: stopping the mover - wheels=%d at Z %.0f"),
WheelComp->Wheels.Num(), GetActorLocation().Z);
}
if (UMovementComponent* Mover = GetVehicleMovementComponent())
{
Mover->SetComponentTickEnabled(false);
WheelComp ? WheelComp->Wheels.Num() : -1, GetActorLocation().Z);
if (UMovementComponent* Mover = GetVehicleMovementComponent())
{
Mover->SetComponentTickEnabled(false);
}
}
}
@@ -53,10 +53,17 @@ UPhysicsAsset* UGigLineVehicleBuilder::BuildPhysicsAsset(USkeletalMesh* Mesh,
// A box slightly under the mesh bounds: a body that is exactly the bounds sits on the
// ground plane and reads as floating because the mesh has wheels below the sills.
FKBoxElem Box;
// The skeletal mesh imports ROLLED (height along Y, width along Z) while the armature is
// unrolled (height Z, width Y). The box is a body on the root bone, so it must be built in
// the UNROLLED frame: length from mesh X, width from mesh Z, height from mesh Y.
Box.X = Extent.X * 1.9f;
Box.Y = Extent.Y * 1.8f;
Box.Z = Extent.Z * 0.55f;
Box.Center = FVector(0.f, 0.f, -Extent.Z * 0.25f);
Box.Y = Extent.Z * 1.8f;
Box.Z = Extent.Y * 0.55f;
// The chassis sits at the car's centre of mass, which is the rolled mesh's mid-height
// (Bounds.Origin.Y) mapped onto the unrolled height axis (Z). The suspension rest position is
// measured against the root bone (the grounded origin), so the box must sit at the centre of
// mass rather than the root bone - this is what settled the vehicle on the road.
Box.Center = FVector(0.f, 0.f, Bounds.Origin.Y);
Body->AggGeom.BoxElems.Add(Box);
PhysicsAsset->SkeletalBodySetups.Add(Body);
+134
View File
@@ -1048,3 +1048,137 @@ Historical task context from 2026-11-09/10; later vehicle findings above may sup
X, not the bone's Y), the engine torque sign, or the throttle sign. Next probe: run the
`-drivetest` with `SetThrottleInput(-1.0)` to split "throttle inverted" from "wheel spin
inverted" in one run.
## 2026-11-10 — Vehicle reverse: both throttle signs drive backward
- `-reversetest` (negative throttle) shows the throttle sign is not the cause: forward
throttle → −224.9 km/h, reverse throttle → −99.5 km/h — **both backward**, so the wheel
spin is inverted relative to the engine torque, not the input mapping.
- The mesh orientation is fine (imported bounds 476 × 228 cm, nose at +X). The earlier axle
flip (inward→outward) was a no-op, so the next check is the **imported wheel-bone
reference pose**: the axle (bone Y) may be landing on Unreal X or Z, not Y, so the wheel
spins about the wrong axis. `FPhysicsAssetUtils`-style body setup is not the cause either
(physics already simulates).
- Kept `-reversetest` in the drive test as the diagnostic for this.
## 2026-11-10 — Vehicle reverse: the skeleton is rolled 90° vs the mesh
- Bone reference poses (`AnimPose.get_ref_bone_pose`) show the armature is **rolled 90°**:
`root` rot (roll 90, 0, 0), `Wheel_FL/RL` rot (90, 0, −180), `Wheel_FR/RR` rot (−90, 0, 0);
wheel centres land at Z ±0.81 instead of Y ±0.81. The mesh vertices are unrotated (bounds
476 × 228 × 144, nose +X), so the **FBX `axis_forward: -Y` conversion rotates the armature
but not the geometry** — the wheel axle (bone Y) ends up on a rotated axis, which is why the
engine torque drives the wheels backward and why the inward/outward axle flip was a no-op.
- Fix belongs in the bone authoring, not the vehicle component: build the wheel bones so their
Y axis lands on Unreal +Y after the FBX conversion, or set `use_space_transform`/bake the
space transform so mesh and armature share one frame. Verify with the reference-pose probe
(`get_ref_bone_pose` → the wheel axle should read ±Y, not ±Z) before re-running the drive
test.
## 2026-11-10 — Vehicle reverse: FBX armature bakes a −90° roll the build can't undo
- Inspecting `vehicle_skeletal.fbx` (imported back into Blender) shows the export bakes a
**−90° rotation about X** into the armature: `root` tail (0,1.64,0), wheel tails at
(x, 0.33, ±0.83) — the wheel **axle lands on Z**, not Y. The mesh vertices are exported
unrotated (bounds 476×228×144), so mesh and armature come back in different frames.
- Three Blender-side fixes were tried and **none changed the imported wheel bones**:
1. axle tail along −Z (head/tail) — reference pose unchanged;
2. `axis_forward: "X"` on the `_CHARACTER` preset — unchanged;
3. armature object pre-roll −90° about X — unrolled the `root` (roll 90→0) but left the
wheel bones at roll ±90 / Z ±0.81.
- Conclusion: the export bakes the armature conversion regardless of head/tail or the
object transform. Remaining options (untried): `bake_space_transform: true` (also rolls
the mesh — would need the mesh un-rolled again in Unreal), or a **post-import skeleton
fix** — rotate each wheel bone's reference pose −90° about X in `ue_vehicle_import.py`
(or a commandlet) so the axle lands on Y. Verify with the `get_ref_bone_pose` probe
(axle should read ±Y) before re-running `-drivetest`.
## 2026-11-10 — Vehicle reverse: not the axle, not the gear ratio
- The reverse direction is **not** the axle orientation and **not** the final-drive ratio:
flipping the wheel-bone axle to horizontal (Unreal ±Y, via `axis_forward: X` + a Blender-Z
tail) changed the reference pose (FL roll 180, FR roll 0) but the `-drivetest` still reads
`speed=-2737 cm/s`. `FinalRatio = -3.5` also changed nothing (it is a magnitude; the gear
direction is `ForwardGearRatios` vs `ReverseGearRatios`).
- So the wheels spin backward for positive torque regardless of axle direction, which points
at the **wheel spin axis convention in the Chaos solver** (the `FSimpleWheel` angular
velocity axis) or the engine torque sign, not the bone authoring. The horizontal-axle fix
is kept (it is correct even if insufficient): `axis_forward: X` in the character preset and
a Blender-Z wheel tail (`r - copysign(0.25, y)`) so the axle lands on Unreal Y.
## 2026-11-10 — Vehicle reverse is in the Chaos solver, not the assets (revert the axis fix)
- Reverted the `axis_forward: X` / Blender-Z axle fix (`ecdd820`): the reference pose changed
but `-drivetest` still read −2737 cm/s, and `axis_forward: X` lives in the shared character
preset, so it would also alter every character export.
- Traced the Chaos solver: the wheel's local frame is `GroundXVector = CrossProduct(VehicleRightAxis,
GroundZVector)` and the drive force is `FinalLongitudinalForce = DriveTorque / Re`, applied forward —
yet the car moves backward. The wheel spins about the **vehicle's** Y, not the bone's, so the bone
authoring is irrelevant. The inversion is in the **engine-torque / drive-force sign** (`FSimpleWheel`),
not the assets. Next: check `GetEngineTorque()` sign, or override `SetupVehicle`/`ProcessMechanicalSimulation`
in `UGigLineVehicleMovement` to negate the drive torque.
## 2026-11-10 — Vehicle: the visual body and the physics body are different vehicles
- `SM_Vehicle_Body` (the owner's **hot hatch** template, what the panels render) imports with
bounds **150 x 400 x 122 cm** — its length is along **Y**. `SK_Vehicle_Sedan` (the procedural
sedan that the Chaos solver drives) is **476 x 228 x 144** with the length along **X**. They
are built by different scripts (`vehicle_from_template.py` vs `vehicle_build.py`) and do not
even share a forward axis.
- This is almost certainly the real story behind the "reverse" drive: the solver applies the
drive force forward on the skeletal sedan (+X) while the visible car's nose is on a different
axis, so a forward physics motion reads as a sideways/backward car. `MaxTorque = -500` was also
a no-op (the torque curve normalises the magnitude), ruling out the engine torque sign.
- Next step is not more solver tracing: reconcile the two meshes — drive the **hot hatch** (make
it skeletal, or rebuild `SK_Vehicle_Sedan` to match the hot hatch's axis and dimensions) so the
visible and physics cars are one asset with one forward axis.
## 2026-11-10 — Hot hatch is now the skeletal (physics) mesh; vehicle still floats
- `vehicle_from_template.py` now builds a skeletal mesh from the hot hatch (the real car):
it applies the MIRROR/NODES modifiers, derives the four wheel centres by quadrant, adds a
root + `Wheel_FL/FR/RL/RR` armature, weights wheels-to-bones, and exports
`vehicle_skeletal.fbx`. A rotation matrix [[0,1,0],[0,0,-1],[-1,0,0]] orients the car so the
FBX conversion lands it unrolled (length +X, width +Y, height +Z). Wheel bones import
correctly: FL (1.43,-0.71,0.34) FR (1.43,0.62,0.34) RL (-1.22,-0.75,0.36) RR (-1.22,0.67,0.36).
- **Still floats** (rises 300 -> 3315 cm, `simulating=1`, 4 wheels): the same suspension push
as before, so the reverse/float is NOT the visual/physics axis mismatch after all. Remaining
suspect is the chassis box centre (`-Extent.Z*0.25` = ~-20) vs the wheel-bone height (0.34):
the suspension rest length is measured against the root BONE, not the box, so the box offset
may be the push. Next: try `Box.Center = (0,0,+Extent.Z*0.3)` or drive the box from the
wheel-bone height, then re-run `-drivetest`.
## 2026-11-10 — Box centre: raising it cuts the float, but it persists
- `Box.Center = Extent.Z*0.35` (was `-Extent.Z*0.25`) cuts the float from ~33 m to **7.82 m**
(`settled at Z 782`, ground at Z 5). Direction confirmed: the chassis box belongs above the
ground, not below the root bone. Still not settled, so the box centre needs to go higher toward
the car's centre of mass (~0.7 m), and/or the root bone should sit at the centre of mass rather
than at the ground (the `normalise_scale` grounds the whole car, so the root bone is at Z 0).
- The hot-hatch skeletal mesh now imports (403 x 148 x 158 cm), but the mesh is rolled (height Y,
width Z) while the armature is correct - the MESH vertices are exported raw (identity) while the
ARMATURE is baked with the FBX conversion, the same mesh-vs-armature split as before.
## 2026-11-10 — Vehicle settles (float 33 m -> 6 cm); drive still ~0
- **Two real fixes** (commit `319096d`):
1. The skeletal mesh imports ROLLED (height along Y, width along Z) while the armature is
unrolled. The chassis box must therefore be built with swapped axes (`Box.Y = Extent.Z*1.8`,
`Box.Z = Extent.Y*0.55`) and centred at the rolled mesh's mid-height (`Box.Center.Z =
Bounds.Origin.Y`). This is what settled the car at Z 6 cm (was floating at 33 m).
2. A diagnostic `if (SettleElapsed > 2.5f) Mover->SetComponentTickEnabled(false)` was firing for
every vehicle (not just zero-wheel ones) and cut the drive test (throttle 1-4 s). Now gated
on `Wheels.Num() == 0`.
- **Experiment that FAILED and was reverted**: moving the root bone to the centre of mass in
`vehicle_from_template.py` (root at `body.bound_box` centre) puts the wheel bones at NEGATIVE Z
(-54 cm below root) and the vehicle then FLOATS at spawn height (stays Z 304, never falls).
So the Chaos suspension in this rig wants a POSITIVE rest position (wheel bone above the root
bone). Root bone stays grounded at (0,0,0); the box offset to centre of mass is done in C++.
- **Remaining**: drive test reports `speed=1 cm/s` (no forward motion) and `travelled ~56 m` - a
horizontal drift during the fall. Wheels rest ~2 cm above the ground (wheel bone Z +34 vs wheel
radius 33) because the suspension rest position is measured against the root bone while the box
sits at +76. Next: make the wheels actually contact (align wheel radius to the bone, or correct
the rest position for the box offset) before re-testing direction.
- `GetWheelRestingPosition` = `LocateBoneOffset(BoneName, WheelClass.Offset + AdditionalOffset)`,
which is the wheel bone position relative to the ROOT BODY BONE - it does NOT account for
`Box.Center`. That offset is the root of the drive gap.
+71
View File
@@ -8,6 +8,7 @@ Actionable task list for this project. Planned work and progress marks live here
- A level-three (`###`) section being worked on carries `- active - <sessionId>` (and optionally the branch name) in its heading. Remove the suffix when you stop.
- When all steps in a version section are done: write a `CHANGELOG.md` entry, then remove the version section from this file.
- Release gate: a version is tagged only on the owner's explicit go-ahead once its release checks pass, not automatically when the checkboxes close.
- Every task section carries a `**Suggested skills:**` line naming the packs to load before starting. Load them with the `skill` tool — `blender-director` for asset pipelines, `ue-project-context` for engine work, then the packs named — and use them as method guides, since this project runs Blender and Unreal headless from scripts.
## Toolchain — end-to-end asset check (done 2026-11-10)
@@ -15,6 +16,8 @@ Actionable task list for this project. Planned work and progress marks live here
See [toolchain notes](MEMORY.md#toolchain--end-to-end-asset-check-done-2026-11-10).
**Suggested skills:** no pack covers the local ComfyUI, judge or driver tooling — work from `Tools/README.md`, `Tools/review/` and the toolchain notes in `MEMORY.md`.
- [x] Group the sixty tools by sandbox: `Tools/unreal/`, `Tools/blender/`, `Tools/review/`, `Tools/gen/`, `Tools/input/`, with the layout documented in `Tools/README.md`.
- [x] Empty both maps and verify that no actors remain using `Tools/unreal/ue_recreate_maps.py`.
- [x] Clear the whole level in both builders.
@@ -34,6 +37,8 @@ See [historical notes](MEMORY.md#vehicle-physics--chaos-rig-own-task-from-030).
### Tasks
**Suggested skills:** `ue-physics-collision`, `ue-module-build-system` (plugin enablement), `ue-actor-component-architecture`, `ue-gameplay-framework`, `ue-input-system`, `ue-testing-debugging`, `cpp`; Blender side `vehicle-artist`, `collision-proxy`, `export-pipeline`, `unreal-export`. No pack covers Chaos Vehicles — the notes in this section and the `ChaosVehiclesPlugin` headers are the reference.
- [x] Give `SK_Vehicle_Sedan` a physics asset whose single chassis body matches the car's box. **Resolved 2026-11-10** — two causes, both fixed: `ChaosVehiclesPlugin` was not enabled in `.uproject`, so `CreateVehicle()` never ran (`0 wheels`); and `BuildPhysicsAsset` never set `Body->PhysicsType`, so the body stayed `PhysType_Default` (follow owner) and never simulated. Enabling the plugin (`5e328f5`) and setting `PhysicsType = PhysType_Simulated` (`927e937`) make the pawn **simulate, settle at Z 77 cm, and drive** (`-drivetest` moved 36.3 m).
- [x] Find what writes the actor transform in `DemoMap` but not in `ReviewMap`. **Resolved** — it was never a transform writer: the body was not simulating (see above), so the "float" and "teleport" were the unsimulated chassis being driven by the regular rigid-body solver instead of the Chaos async solver.
- [x] Confirm `Wheels.Num()` becomes 4 once the vehicle is created; log it at the settle check. **4 wheels** once the plugin is enabled (`setups=4 wheels=4`).
@@ -45,6 +50,8 @@ See [historical notes](MEMORY.md#hero-facade-rework--from-the-reviewmap-review-o
### Tasks
**Suggested skills:** `blender-director`, `archviz`, `historical-worlds`, `realistic-style`, `hard-surface` (relief and mouldings), `materials`, `texture-workflow`, `uv-workflow`, `lookdev`, `rendering`, `camera-cinematography`, `references` (visual-match checklist), `qa-review`; engine side `ue-materials-rendering`.
- [x] Confirm the hero captures frame `Hero_Facade`.
- [x] Seat the elevation detail in front of the mass.
- [x] Remove the blank lintel band across the portals.
@@ -66,6 +73,8 @@ See [historical notes](MEMORY.md#hero-facade-rework--from-the-reviewmap-review-o
See [vehicle production notes](MEMORY.md#030--chaos-vehicle-enterexit-closed-driving-route-weeks-1013).
**Suggested skills:** `blender-director`, `vehicle-artist`, `hard-surface`, `materials`, `uv-workflow`, `collision-proxy`, `lod-pipeline`, `asset-optimization`, `export-pipeline`, `unreal-export` (against `ue_metric_v1`); engine side `ue-project-context` first, then `ue-physics-collision`, `ue-actor-component-architecture`, `ue-gameplay-framework`, `ue-input-system`, `ue-character-movement` (enter/exit), `ue-world-level-streaming` (route bounds), `ue-testing-debugging`, `cpp`.
- [A] Model one vehicle in Blender with collision meshes; export to Unreal.
- [D] Set up Chaos Vehicle with wheel and physics configuration.
- [x] Fix the vehicle not rendering in the scene.
@@ -89,6 +98,8 @@ See [vehicle production notes](MEMORY.md#030--chaos-vehicle-enterexit-closed-dri
### Patch tasks
**Suggested skills:** `ue-physics-collision`, `ue-input-system`, `ue-actor-component-architecture`, `ue-testing-debugging`, `cpp`; `vehicle-artist` only if the fix is visual mass or wheel placement.
- [ ] Refine vehicle handling feel based on `0.3.0` feedback.
- [ ] Tag `v0.3.1` and write `CHANGELOG.md` entry; then remove this section.
@@ -96,6 +107,8 @@ See [vehicle production notes](MEMORY.md#030--chaos-vehicle-enterexit-closed-dri
### Patch tasks
**Suggested skills:** `collision-proxy`, `vehicle-artist`, `asset-optimization`, `export-pipeline`, `unreal-export`, `ue-physics-collision`, `ue-testing-debugging`.
- [ ] Fix vehicle collision defects (mesh/physics mismatches).
- [ ] Tag `v0.3.2` and write `CHANGELOG.md` entry; then remove this section.
@@ -103,6 +116,8 @@ See [vehicle production notes](MEMORY.md#030--chaos-vehicle-enterexit-closed-dri
### Patch tasks
**Suggested skills:** `ue-input-system`, `ue-character-movement`, `ue-gameplay-framework`, `ue-blueprint-cpp-interop`, `ue-testing-debugging`.
- [ ] Fix vehicle/character input issues in `0.3.x`.
- [ ] Tag `v0.3.3` and write `CHANGELOG.md` entry; then remove this section.
@@ -110,6 +125,8 @@ See [vehicle production notes](MEMORY.md#030--chaos-vehicle-enterexit-closed-dri
### World expansion
**Suggested skills:** `blender-director`, `environment-artist`, `procedural-modeling`, `geometry-nodes`, `archviz`, `scene-assembly`, `asset-optimization`, `lod-pipeline`, `uv-workflow`, `materials`, `export-pipeline`, `unreal-export`; engine side `ue-project-context` first, then `ue-world-level-streaming`, `ue-procedural-generation`, `ue-async-threading`, `ue-materials-rendering`, `ue-testing-debugging`, `cpp`.
- [ ] Extend geo pipeline to a small connected neighborhood beyond the demo block.
- [ ] Produce modular buildings, sidewalks, and roads for the expanded area.
- [ ] Set up World Partition with streaming cells.
@@ -127,6 +144,8 @@ See [vehicle production notes](MEMORY.md#030--chaos-vehicle-enterexit-closed-dri
### Patch tasks
**Suggested skills:** `ue-world-level-streaming`, `asset-optimization`, `environment-artist`, `ue-testing-debugging`.
- [ ] Fix visible seams between streamed cells.
- [ ] Tag `v0.4.1` and write `CHANGELOG.md` entry; then remove this section.
@@ -134,6 +153,8 @@ See [vehicle production notes](MEMORY.md#030--chaos-vehicle-enterexit-closed-dri
### Patch tasks
**Suggested skills:** `ue-world-level-streaming`, `ue-async-threading`, `ue-testing-debugging`.
- [ ] Correct streaming/loading issues found in `0.4.x`.
- [ ] Tag `v0.4.2` and write `CHANGELOG.md` entry; then remove this section.
@@ -141,6 +162,8 @@ See [vehicle production notes](MEMORY.md#030--chaos-vehicle-enterexit-closed-dri
### Patch tasks
**Suggested skills:** `lod-pipeline`, `ue-world-level-streaming`, `asset-optimization`, `ue-testing-debugging`.
- [ ] Tune HLOD quality and distance settings.
- [ ] Tag `v0.4.3` and write `CHANGELOG.md` entry; then remove this section.
@@ -148,6 +171,8 @@ See [vehicle production notes](MEMORY.md#030--chaos-vehicle-enterexit-closed-dri
### Simulation
**Suggested skills:** `ue-project-context` first, then `ue-ai-navigation`, `ue-character-movement`, `ue-actor-component-architecture`, `ue-gameplay-framework`, `ue-state-trees`, `ue-mass-entity` (crowd scale), `ue-async-threading`, `ue-testing-debugging`; the `AGigLineNPCPerson` prefab is Blender work — `character-artist`, `rigging`, `animation`, `export-pipeline`, `unreal-export`.
- [ ] Add pedestrian characters with navigation, using `AGigLineNPCPerson` (the reusable NPC prefab) as the base, driving it via AI/`AutoPossessAI`.
- [ ] Implement crosswalk behavior (pedestrians use the designated crossing).
- [ ] Add limited ambient traffic on the street network.
@@ -163,6 +188,8 @@ See [vehicle production notes](MEMORY.md#030--chaos-vehicle-enterexit-closed-dri
### Patch tasks
**Suggested skills:** `ue-ai-navigation`, `ue-physics-collision`, `ue-testing-debugging`.
- [ ] Fix pedestrian navigation issues.
- [ ] Tag `v0.5.1` and write `CHANGELOG.md` entry; then remove this section.
@@ -170,6 +197,8 @@ See [vehicle production notes](MEMORY.md#030--chaos-vehicle-enterexit-closed-dri
### Patch tasks
**Suggested skills:** `ue-ai-navigation`, `ue-state-trees`, `ue-gameplay-tags-messaging`, `ue-testing-debugging`.
- [ ] Fix crosswalk behavior defects.
- [ ] Tag `v0.5.2` and write `CHANGELOG.md` entry; then remove this section.
@@ -177,6 +206,8 @@ See [vehicle production notes](MEMORY.md#030--chaos-vehicle-enterexit-closed-dri
### Patch tasks
**Suggested skills:** `ue-mass-entity`, `ue-async-threading`, `ue-testing-debugging`, `asset-optimization`, `lod-pipeline`.
- [ ] Optimize pedestrian/traffic simulation cost.
- [ ] Tag `v0.5.3` and write `CHANGELOG.md` entry; then remove this section.
@@ -186,6 +217,8 @@ See [vehicle production notes](MEMORY.md#030--chaos-vehicle-enterexit-closed-dri
See [pavement notes](MEMORY.md#060--visualaudio-pass-and-evening-lighting-preset-weeks-2224).
**Suggested skills:** `lighting`, `rendering`, `lookdev`, `materials`, `texture-workflow`, `archviz`, `environment-artist`, `blender-director`, `references` (visual-match checklist), `qa-review`; engine side `ue-materials-rendering`, `ue-niagara-effects`, `ue-audio-system`, `ue-gameplay-cameras`, `ue-testing-debugging`.
- [ ] Create an evening lighting preset.
- [ ] Complete a visual polish pass across the neighborhood.
- [ ] Model the pavement between kerb and building line.
@@ -203,6 +236,8 @@ See [pavement notes](MEMORY.md#060--visualaudio-pass-and-evening-lighting-preset
### Patch tasks
**Suggested skills:** `lighting`, `lookdev`, `rendering`, `references`, `qa-review`, `ue-materials-rendering`.
- [ ] Fix lighting defects in day/evening presets.
- [ ] Tag `v0.6.1` and write `CHANGELOG.md` entry; then remove this section.
@@ -210,6 +245,8 @@ See [pavement notes](MEMORY.md#060--visualaudio-pass-and-evening-lighting-preset
### Patch tasks
**Suggested skills:** `materials`, `texture-workflow`, `lookdev`, `qa-review`, `ue-materials-rendering`.
- [ ] Fix material errors found in `0.6.x`.
- [ ] Tag `v0.6.2` and write `CHANGELOG.md` entry; then remove this section.
@@ -217,6 +254,8 @@ See [pavement notes](MEMORY.md#060--visualaudio-pass-and-evening-lighting-preset
### Patch tasks
**Suggested skills:** `ue-audio-system`, `ue-testing-debugging`.
- [ ] Balance audio levels across the scene.
- [ ] Tag `v0.6.3` and write `CHANGELOG.md` entry; then remove this section.
@@ -224,6 +263,8 @@ See [pavement notes](MEMORY.md#060--visualaudio-pass-and-evening-lighting-preset
### Systems
**Suggested skills:** `ue-project-context` first, then `ue-ui-umg-slate`, `ue-input-system`, `ue-serialization-savegames`, `ue-data-assets-tables`, `ue-gameplay-framework`, `ue-blueprint-cpp-interop`, `cpp`, `ue-testing-debugging`.
- [ ] Build settings menu (graphics and audio options).
- [ ] Implement control rebinding with persistence.
- [ ] Implement save/load for player state.
@@ -239,6 +280,8 @@ See [pavement notes](MEMORY.md#060--visualaudio-pass-and-evening-lighting-preset
### Patch tasks
**Suggested skills:** `ue-serialization-savegames`, `ue-testing-debugging`, `cpp`.
- [ ] Fix save/load defects.
- [ ] Tag `v0.7.1` and write `CHANGELOG.md` entry; then remove this section.
@@ -246,6 +289,8 @@ See [pavement notes](MEMORY.md#060--visualaudio-pass-and-evening-lighting-preset
### Patch tasks
**Suggested skills:** `ue-ui-umg-slate`, `ue-testing-debugging`.
- [ ] Fix UI issues in `0.7.x`.
- [ ] Tag `v0.7.2` and write `CHANGELOG.md` entry; then remove this section.
@@ -253,6 +298,8 @@ See [pavement notes](MEMORY.md#060--visualaudio-pass-and-evening-lighting-preset
### Patch tasks
**Suggested skills:** `ue-input-system`, `ue-serialization-savegames`, `ue-testing-debugging`.
- [ ] Fix rebinding persistence issues.
- [ ] Tag `v0.7.3` and write `CHANGELOG.md` entry; then remove this section.
@@ -260,6 +307,8 @@ See [pavement notes](MEMORY.md#060--visualaudio-pass-and-evening-lighting-preset
### Performance
**Suggested skills:** `ue-testing-debugging` (Unreal Insights, `stat unit`, `memreport`), `asset-optimization`, `lod-pipeline`, `ue-materials-rendering` (scalability and upscaler state), `ue-async-threading`, `ue-world-level-streaming`, `ue-niagara-effects`, `cpp`.
- [ ] Define performance budgets (frame time, VRAM) and document them.
- [ ] Profile the full neighborhood; fix budget violations.
- [ ] Test on broader Linux hardware and VRAM configurations; document results.
@@ -275,6 +324,8 @@ See [pavement notes](MEMORY.md#060--visualaudio-pass-and-evening-lighting-preset
### Patch tasks
**Suggested skills:** `ue-materials-rendering` (texture streaming, LOD bias), `asset-optimization`, `texture-workflow`, `lod-pipeline`, `ue-testing-debugging`.
- [ ] Reduce VRAM usage where profiling shows excess.
- [ ] Tag `v0.8.1` and write `CHANGELOG.md` entry; then remove this section.
@@ -282,6 +333,8 @@ See [pavement notes](MEMORY.md#060--visualaudio-pass-and-evening-lighting-preset
### Patch tasks
**Suggested skills:** `ue-testing-debugging`, `ue-async-threading`, `ue-niagara-effects`, `asset-optimization`, `lod-pipeline`.
- [ ] Fix frame-time spikes found in `0.8.x`.
- [ ] Tag `v0.8.2` and write `CHANGELOG.md` entry; then remove this section.
@@ -289,6 +342,8 @@ See [pavement notes](MEMORY.md#060--visualaudio-pass-and-evening-lighting-preset
### Patch tasks
**Suggested skills:** `ue-testing-debugging`, `ue-module-build-system` (build and package differences), `ue-materials-rendering` (GPU and driver-specific paths).
- [ ] Fix compatibility issues from the hardware testing matrix.
- [ ] Tag `v0.8.3` and write `CHANGELOG.md` entry; then remove this section.
@@ -298,6 +353,8 @@ See [realistic building notes](MEMORY.md#090--realistic-buildings-weeks-3134).
### Façade system
**Suggested skills:** `blender-director`, `archviz`, `historical-worlds`, `realistic-style`, `procedural-modeling`, `geometry-nodes`, `hard-surface`, `materials`, `texture-workflow`, `uv-workflow`, `asset-optimization`, `lod-pipeline`, `export-pipeline`, `unreal-export`, `references`, `qa-review`; engine side `ue-materials-rendering`.
- [ ] **Extend the module kit beyond one order.** Art Deco, neoclassical and streamline-moderne variants on the shared 4.96 m bay: alternate window proportions, spandrel and transom bands, and the way each order terminates at the roof.
- [ ] **Build a profile library** for cornices, copings, string courses, sills and storefront surrounds, so a building is assembled from named profiles rather than ad-hoc boxes.
- [ ] **Storefront types**: recessed entry, bulkhead and kickplate, mullion spacing, transom, awning/canopy brackets, and grille/shutter variants.
@@ -306,6 +363,8 @@ See [realistic building notes](MEMORY.md#090--realistic-buildings-weeks-3134).
### Per-building models
**Suggested skills:** `references` (reference-image-match and visual-match), `camera-cinematography` (match the photograph's angle before detail), `archviz`, `historical-worlds`, `realistic-style`, `blender-modeler`, `hard-surface`, `prop-artist` (street-level details), `materials`, `texture-workflow`, `uv-workflow`, `qa-review`, `blender-director`.
- [ ] Use a building-specific reference set for each building; retain blockouts and record buildings without references.
- [ ] Hero façade (6840): add volute capital depth, lattice grille depth, window rhythm behind the colonnade, and heavy banded rustication.
@@ -320,6 +379,8 @@ See [realistic building notes](MEMORY.md#090--realistic-buildings-weeks-3134).
### Materials, UVs and performance
**Suggested skills:** `materials`, `texture-workflow`, `uv-workflow`, `lookdev`, `rendering`, `collision-proxy`, `lod-pipeline`, `asset-optimization`, `ue-materials-rendering`, `ue-testing-debugging`.
- [ ] **Measured material library**, including the hero's stone: stucco, painted render, ashlar, brick, terrazzo, glazing, bronze and steel trim, with albedo and roughness taken from the photographs rather than guessed; one Unreal material instance per surface type.
- [ ] **Texel-density rule** per building class, applied through modular UVs; lightmap UVs where required.
- [ ] **LOD chain and collision proxies** per building: UCX proxies for blocking volume, detail geometry non-colliding.
@@ -339,6 +400,8 @@ See [realistic building notes](MEMORY.md#090--realistic-buildings-weeks-3134).
### Freeze and audit
**Suggested skills:** `ue-project-context` (refresh before the audit), `ue-module-build-system` (reproducible build), `ue-testing-debugging`, `qa-review`, `de-ai-writing` (`Docs/` prose).
- [ ] Freeze features; document the freeze in `CHANGELOG.md`.
- [ ] Produce a public beta build for testers.
- [ ] Complete license audit: `THIRD_PARTY_NOTICES.md`, all MPFB assets, textures, animations, vehicles, audio.
@@ -354,6 +417,8 @@ See [realistic building notes](MEMORY.md#090--realistic-buildings-weeks-3134).
### Patch tasks
**Suggested skills:** `ue-testing-debugging` plus the pack for the subsystem each blocker lives in (see the mapping in `AGENTS.md`); `cpp` for non-engine code.
- [ ] Fix blockers reported during the public beta.
- [ ] Tag `v0.10.1` and write `CHANGELOG.md` entry; then remove this section.
@@ -361,6 +426,8 @@ See [realistic building notes](MEMORY.md#090--realistic-buildings-weeks-3134).
### Patch tasks
**Suggested skills:** `ue-testing-debugging`, plus the owning subsystem's pack, and `cpp` where the regression is in game code; check `MEMORY.md` for how the original defect was found.
- [ ] Fix regressions introduced by `0.9.x` fixes.
- [ ] Tag `v0.10.2` and write `CHANGELOG.md` entry; then remove this section.
@@ -368,6 +435,8 @@ See [realistic building notes](MEMORY.md#090--realistic-buildings-weeks-3134).
### Patch tasks
**Suggested skills:** `de-ai-writing` (prose), `ue-project-context` (verify the project facts it records), `ue-module-build-system` (verify the build and checkout steps documented).
- [ ] Update all `Docs/` for accuracy before `1.0.0`.
- [ ] Tag `v0.10.3` and write `CHANGELOG.md` entry; then remove this section.
@@ -375,6 +444,8 @@ See [realistic building notes](MEMORY.md#090--realistic-buildings-weeks-3134).
### Final release
**Suggested skills:** `qa-review`, `references` (final visual-match sweep), `ue-testing-debugging`, `de-ai-writing` (`Docs/`), `ue-project-context` refresh.
- [ ] Complete final acceptance sweep across all `0.x` features.
- [ ] Confirm the stable, agreed game scope is met (neighborhood-scale walking and driving).
- [ ] Final build, documentation, and licensing verification.
+122
View File
@@ -230,6 +230,90 @@ def join_system(objects, name):
return joined
def build_skeletal(joined, args):
"""Bind the body + wheels to a root + 4 wheel bones for the Chaos vehicle.
The hot hatch's nose is along +Y, so wheel front/rear is the Y sign and left/right is the
X sign (the wheels are mirrored in the template, so the joined mesh has all four). Each
wheel is weighted to its own bone at full weight; the body goes to the root.
"""
import math
body = joined["body"]
wheels = joined["wheels"]
# Derive the four wheel centres from the wheel geometry: group vertices by quadrant.
# After the Unreal-frame rotation, front/rear is the X sign and left/right is the Z sign.
quadrants = {"FL": [], "FR": [], "RL": [], "RR": []}
for vertex in wheels.data.vertices:
world = wheels.matrix_world @ vertex.co
key = ("F" if world.x > 0 else "R") + ("L" if world.z > 0 else "R")
quadrants[key].append(world)
centres = {}
for key, verts in quadrants.items():
if not verts:
continue
centres[f"Wheel_{key}"] = (
sum(v.x for v in verts) / len(verts),
sum(v.y for v in verts) / len(verts),
sum(v.z for v in verts) / len(verts),
)
if len(centres) != 4:
raise SystemExit(f"expected 4 wheel quadrants, got {len(centres)}: {list(centres)}")
log(f"wheel centres: " + ", ".join(f"{k}=({v[0]:.2f},{v[1]:.2f},{v[2]:.2f})"
for k, v in sorted(centres.items())))
bpy.ops.object.armature_add(location=(0.0, 0.0, 0.0))
rig = bpy.context.active_object
rig.name = "VehicleRig"
bpy.ops.object.mode_set(mode="EDIT")
edit_bones = rig.data.edit_bones
root = edit_bones[0]
root.name = "root"
root.head = (0.0, 0.0, 0.0)
root.tail = (0.0, 0.0, 0.30)
for name, (x, y, z) in sorted(centres.items()):
bone = edit_bones.new(name)
bone.head = (x, y, z)
# Axle direction is irrelevant to the Chaos solver (it drives the wheel about the
# vehicle's Y), so point the tail straight down for a stable reference frame.
bone.tail = (x, y, z - 0.25)
bone.parent = root
bpy.ops.object.mode_set(mode="OBJECT")
# Join the body and wheels into one skinned mesh.
bpy.ops.object.select_all(action="DESELECT")
body.select_set(True)
wheels.select_set(True)
bpy.context.view_layer.objects.active = body
bpy.ops.object.join()
mesh = bpy.context.active_object
mesh.name = "SM_Vehicle_Skeletal"
groups = {name: mesh.vertex_groups.new(name=name) for name in ["root", *centres]}
for vertex in mesh.data.vertices:
world = mesh.matrix_world @ vertex.co
nearest, best = "root", 0.6 ** 2
for name, (x, y, z) in centres.items():
d = (world.x - x) ** 2 + (world.y - y) ** 2 + (world.z - z) ** 2
if d < best:
best, nearest = d, name
groups[nearest].add([vertex.index], 1.0, "REPLACE")
armature = mesh.modifiers.new(name="Armature", type="ARMATURE")
armature.object = rig
mesh.parent = rig
bpy.ops.object.select_all(action="DESELECT")
rig.select_set(True)
mesh.select_set(True)
bpy.context.view_layer.objects.active = rig
path = os.path.join(args.out, "vehicle_skeletal.fbx")
fbx_export_preset.export_character(path, selection=True)
log(f"skeletal vehicle exported: {path}, bones root + 4 wheels")
return path
def add_collision(body):
"""One convex box per major volume, `UCX_`-prefixed for Unreal."""
hulls = []
@@ -282,6 +366,38 @@ def main(argv=None) -> int:
normalise_scale(meshes)
# Apply the MIRROR modifier so the left wheels exist in the mesh data BEFORE the nose
# rotation; the mirror is in the object's local space, so rotating first would mirror
# about a rotated axis.
for obj in meshes:
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
for modifier in list(obj.modifiers):
try:
bpy.ops.object.modifier_apply(modifier=modifier.name)
except RuntimeError:
pass
obj.select_set(False)
# Rotate so the FBX axis conversion (Blender Y->Unreal -Z, Z->-Y) lands the car unrolled:
# length along +X (forward), width along Y, height along Z. The mapping is X->-Z, Y->X,
# Z->-Y, i.e. the rotation matrix [[0,1,0],[0,0,-1],[-1,0,0]].
from mathutils import Matrix
rot = Matrix((
(0.0, 1.0, 0.0),
(0.0, 0.0, -1.0),
(-1.0, 0.0, 0.0),
))
for obj in meshes:
obj.rotation_euler = rot.to_euler()
bpy.ops.object.select_all(action="DESELECT")
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.transform_apply(location=False, rotation=True, scale=False)
obj.select_set(False)
bpy.context.view_layer.update()
log("rotated to the Unreal frame (length +X, width +Y, height +Z)")
systems = {}
for obj in meshes:
systems.setdefault(classify(obj), []).append(obj)
@@ -363,6 +479,12 @@ def main(argv=None) -> int:
with open(args.report, "w") as handle:
json.dump(report, handle, indent=2)
print(f"[hatch] report -> {args.report}")
# Build the skeletal (physics) mesh last, after the report has recorded the separate
# systems: build_skeletal joins the body and wheels, so it must not run before the
# static exports and report are written.
build_skeletal(joined, args)
return 0