From c748a7dd1e34b5198aa82845a1336332d7b3a8f3 Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Thu, 17 Sep 2026 02:51:45 +0300 Subject: [PATCH] Custom model sedan was integrated. --- assets/blender/vehicles/sedan-v2.blend | 4 +- .../editScene/demos/demo-taxi/PLAN.md | 152 +- .../demos/demo-taxi/animation_tree.json | 1256 +++++++++ .../editScene/demos/demo-taxi/demo_main.cpp | 1234 ++++++++- .../demos/demo-taxi/demo_scene_exterior.json | 2381 +++++++++++------ .../demo-taxi/demo_scene_vehicletest.json | 688 +++++ .../demos/demo-taxi/merge_vehicle.py | 71 + .../demos/demo-taxi/stage_runtime.cmake | 15 +- 8 files changed, 5017 insertions(+), 784 deletions(-) create mode 100644 src/features/editScene/demos/demo-taxi/animation_tree.json create mode 100644 src/features/editScene/demos/demo-taxi/demo_scene_vehicletest.json create mode 100644 src/features/editScene/demos/demo-taxi/merge_vehicle.py diff --git a/assets/blender/vehicles/sedan-v2.blend b/assets/blender/vehicles/sedan-v2.blend index d9d506c..8af8ff6 100644 --- a/assets/blender/vehicles/sedan-v2.blend +++ b/assets/blender/vehicles/sedan-v2.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:48954699f9d36c0dde48b7faaf22dab1813d648f43b8db7787a036a949a1218b -size 1767920 +oid sha256:2a017ceeb809a662b92cfbb8f6fa9577e4a95bfc860164433cd5a4c38feeca4b +size 1753376 diff --git a/src/features/editScene/demos/demo-taxi/PLAN.md b/src/features/editScene/demos/demo-taxi/PLAN.md index fbe6b9b..f7d8179 100644 --- a/src/features/editScene/demos/demo-taxi/PLAN.md +++ b/src/features/editScene/demos/demo-taxi/PLAN.md @@ -197,7 +197,7 @@ cd build-vscode/src/features/editScene/demos/demo-taxi ./demoTaxi --headless --test-switch # inherited scene contract ``` -### Phase 1 — Sedan vehicles +### Phase 1 — Sedan vehicles — DONE - New sedan meshes through the Blender asset pipeline (pattern: `gen_sokoban_assets.py`), using the reference models @@ -213,15 +213,149 @@ cd build-vscode/src/features/editScene/demos/demo-taxi in Phase 11. - Car-tuned `VehicleComponent`: front-wheel steering (no `rearSteer`), no fork, higher `maxTorque`/gearing than the forklift, sedan - `cameraDistance`/`cameraHeight`, `seatOffset`. + `cameraDistance`/`cameraHeight`, multi-seat `seats` list, + `seatCharacterScale`. - Demo-owned `animation_tree.json` (copy demo-sokoban's — it carries - the "driving" locomotion state). + the "driving" locomotion state, here mapped to the `sitting-chair` + animation which fits a car cabin much better than the stool-sit + `sitting`). - Two player sedans parked in the exterior scene near the content - site, enterable via the existing `vehicle_drive` actuator action. - Only one of them is the taxi and starts the taxi loop; the other is - for plain driving. + site. Each has 4 seats (`VehicleComponent::seats`: driver + front-left, passenger front-right, rl/rr); each door child entity + carries an actuator running a generic `vehicle_seat_*` action + (vehicleEnter Lua node -> "vehicle_enter" event with a `seat` param, + the vehicle resolved by proximity), so the player sits at the seat + whose door they used — driving only works from the driver seat + (multi-seat support in `VehicleComponent`/`VehicleControllerSystem`: + seat occupancy, passengers ride without controls). - Headless: `--test-vehicle-drive`-equivalent for the sedan (spawn, - enter, drive, steer, brake, exit) on a flat test scene. + enter, drive, steer, brake, exit — plus a passenger-seat leg: enter + passenger seat, no `PlayerControlledComponent`, W does not drive) on + a flat test scene. + +**As built.** Generator `assets/blender/scripts/gen_taxi_assets.py` +(registered in `assets/blender/vehicles/CMakeLists.txt`) produces +`assets/blender/vehicles/sedan-a.blend` + `sedan-taxi.blend`; the +vehicle export stages into `resources/vehicles/`. When hand-modeling +replacement sedans (or any new car), follow +`assets/blender/vehicles/CAR_MODELING.md` — it documents the full +contract (axis/origin rules, required parts and hinge pivots, cabin +dimensions matching the seated character, scene JSON wiring, glass +material naming, multi-look strategy): + +- Bodies: `sedan-a-blue/red/grey.mesh`, `sedan-taxi.mesh` (yellow, + roof sign). Roof ended up 1.60 m above ground, not 1.2 m — a seated + character's head clips through a 1.2 m roof. The side skirts + (sills + front/rear side panels) reach 9 cm off the ground so the + sit pose's feet (which dangle below the floor pan) stay hidden from + side views. +- Hinge-local part meshes per body: `-door-fl/-fr/-rl/-rr`, `-hood`, + `-trunk`; shared `sedan-wheel.mesh` + `sedan-steering-wheel.mesh`. + Hinge pivots in chassis space: doors FL/RL (left = +X) at + (0.855, -0.10, 0.55) / (0.855, -0.10, -0.46), FR/RR mirrored at + x = -0.855; hood (0, 0.17, 0.75); trunk (0, 0.27, -1.35). Scene + entities hang the parts as children at those pivots. +- Wheels at (±0.8, -0.25, ±1.5), r = 0.33, w = 0.22. + +Demo side: `demo_scene_vehicletest.json` (flat 200 x 200 floor — +sedans outrun the 60 x 60 interior floor; mesh +`DemoFloorPlaneVehicleTest` created in `createDemoResources()`) with +`sedan1` (blue), and `sedan_taxi` (id 600, (4006, 10.8, 3965)) + +`sedan_plain` (id 601, (4018, 10.8, 3965)) in +`demo_scene_exterior.json`. Each sedan has a 4-seat +`VehicleComponent::seats` list (driver/passenger at +(±0.35, -0.22, 0.25), rl/rr at (±0.35, -0.22, -0.35), driver = +front-left) and `seatCharacterScale` 0.85 — the stock chair-sit pose +is proportioned for a chair (butt-to-feet 0.76 m), so the seated +character is scaled down a little to fit the cabin (head 4 cm under +the headliner, feet above the skirts); the scale is applied on enter +and restored on exit by `VehicleControllerSystem`. Each of the 4 +door children carries an actuator with a `vehicle_seat_driver` / +`vehicle_seat_passenger` / `vehicle_seat_rl` / `vehicle_seat_rr` +action (generic actions in `lua-scripts/data2.lua` sending +"vehicle_enter" with a `seat` param; empty `vehicle` param = nearest +vehicle to the player). +`VehicleComponent`: maxTorque 300, engineBrake 0.5, front steering +(35 deg), rear driven, handbrake 300, suspension 0.1/0.35/1.8/0.7, +cameraDistance 6.0, cameraHeight 1.6, steering wheel at +(0.35, 0.45, 0.2) tilt 25 deg ratio 4. + +Window glass: the exported `sedan-glass` is plain opaque (hides the +cabin completely). `fixupSedanGlassMaterial()` (demo_main.cpp) makes +`sedan-glass` alpha-blended and clones an opaque +`sedan-glass-opaque`; `SedanGlassLodListener` swaps every vehicle's +glass submeshes between the two by camera distance (10 m threshold) — +Ogre 14 no longer applies `Material::setLodLevels` to entities, so a +real LOD material never switches. + +**sedan-v2 (hand-modeled base sedan, current reference).** The +hand-modeled `assets/blender/vehicles/sedan-v2.blend` replaced the +procedural sedan as the base plain sedan (`sedan1` in +`demo_scene_vehicletest.json`, `sedan_plain` in the exterior scene). +It goes through the new convention-based generator +`assets/blender/scripts/gen_vehicle.py` (`VEHICLES_GEN_SRC` in +`assets/blender/vehicles/CMakeLists.txt`): names parts by convention +(`* Tire`, `* Door`, `* hood`, `* trunk`, `* Steering`, `seat_*` +empties — exactly one with "driver" in the name), normalizes +wheel/steering mesh axes, renames mesh data/materials to +`sedan-v2-*`, unpacks the color atlas to `sedan-v2-atlas.png`, and +emits `sedan-v2.vehicle.json` — a complete scene entity (box collider +from the shell bounds, wheels with suspension attach lifted by +L_rest = maxLength - g/(2*pi*freq)^2 so the car rides at the modeled +stance, seats from the empties, door/hood/trunk children with +`vehicle_seat_*` actuators). Seat pins are the character ROOT +position — the `driving` state now plays the new `sitting-vehicle` +animation (authored so the base position is the seat point; no +offsets, `seatCharacterScale` 1.0). `sedan-v2-glass` was added to +`kGlassMaterialNames` for the near/far glass swap. The full contract +is `assets/blender/vehicles/CAR_MODELING.md`; the taxi variant of +sedan-v2 is next. One engine fix this exposed: +`AnimationTreeSystem` now disables animations that fall out of the +evaluated set in a single frame (a large-deltaTime fade-out step used +to leave the old animation enabled at its last weight forever — the +stuck `idle` lifted the seated pose through the roof). + +Tuning after the first drives: the stock suspension (1.8 Hz / 0.7 +damping) was pogo-stiff, so gen_vehicle.py now emits 1.3 Hz / 0.6. +And stock Jolt tire friction (peak 1.2 combined with ground via +sqrt) drove like wet ice at speed, so `VehicleComponent` grew +`tireFrictionLongitudinal` / `tireFrictionLateral` curve multipliers +(1.0 = stock; sedans use 1.3 / 3.0 — measured with +`--test-vehicle-turn`: full lock at 15.5 m/s turns 44 deg at stock +grip, 88 deg at 3.0; editable in the Vehicle editor, serialized). +The high grip made hard turns trip the car toward two wheels, so +`antiRollBarStiffness` (Jolt VehicleAntiRollBar per axle pair, sedans +use 2000) was added the same way, and VehicleSystem now applies a +righting torque past ~35 deg of tilt (plus roll rocking damping past +~5 deg) so a car stuck on two wheels recovers on its own — +`--test-vehicle-righting` rolls sedan1 50 deg and requires it back +near-upright. Exiting also got two fixes: the E/F/I/Space pressed +edges now ignore SDL key repeats (holding E no longer re-fires the +door actuators), and after an exit the E key must be released once +before `vehicle_enter` is accepted again +(VehicleControllerSystem::m_eReleaseRequired), and the exit spot is +seat-aware: the character steps out beside the door of the seat it +actually occupied (own side at the seat's chassis-space row first, +then the opposite side of the same row, then behind/ahead of the car) +instead of always beside the chassis center. The drive test's +passenger leg asserts the exit lands on the seat's side. Wheel +visuals: Jolt reports every wheel with the same axle direction, so a +single shared wheel mesh had its inner face outward on the right side — +`VehicleSystem::postPhysicsUpdate` now flips right-side (chassis -X) +wheel visuals 180 deg about the local forward axis; no mirrored mesh +needed. +Note the scene JSONs hold a merged COPY of the +generated entity — after editing the blend, run the single +`update_vehicles` build target (assets/blender/vehicles/CMakeLists.txt): +it re-exports changed blends, re-stages the editScene resources and +re-merges both demo-taxi scenes via +`demos/demo-taxi/merge_vehicle.py` (preserves id + transform). + +Verify: +```bash +./demoTaxi --headless --test-vehicle-drive demo_scene_vehicletest.json +./demoTaxi --headless --test-vehicle-drive # exterior streaming terrain +``` ### Phase 2 — Road geometry & compliance improvements @@ -375,8 +509,8 @@ taxi-specific): - Passenger NPCs waiting at curbs/POIs; hail interaction (E prompt via ActuatorSystem or proximity hail while driving slowly). - NPC boarding/alighting: walk to the car door, play sit state, ride - as passenger (new passenger-seat support — vehicle carries extra - seated NPCs; extends `VehicleComponent` with a seat list), walk away + as passenger (the Phase 1 `VehicleComponent::seats` list tracks + occupancy for the player; extend it to NPC occupants), walk away at dropoff. - Destination assignment: passenger names a POI/curb from the generated town (any settlement in the world); route distance diff --git a/src/features/editScene/demos/demo-taxi/animation_tree.json b/src/features/editScene/demos/demo-taxi/animation_tree.json new file mode 100644 index 0000000..b6d0721 --- /dev/null +++ b/src/features/editScene/demos/demo-taxi/animation_tree.json @@ -0,0 +1,1256 @@ +{ + "trees": [ + { + "name": "male1_6", + "root": { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "sitting-vehicle", + "type": "animation" + } + ], + "name": "driving", + "type": "state" + }, + { + "children": [ + { + "animationName": "idle", + "type": "animation" + } + ], + "name": "idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "walking", + "type": "animation" + } + ], + "name": "walking", + "type": "state" + }, + { + "children": [ + { + "animationName": "running", + "type": "animation" + } + ], + "name": "running", + "type": "state" + }, + { + "children": [ + { + "animationName": "treading_water", + "type": "animation" + } + ], + "name": "swim-idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "swimming", + "type": "animation" + } + ], + "name": "swimming", + "type": "state" + }, + { + "children": [ + { + "children": [ + { + "animationName": "swimming", + "type": "animation" + } + ], + "speed": 10.0, + "type": "speed" + } + ], + "name": "swimming-fast", + "type": "state" + } + ], + "name": "locomotion", + "type": "stateMachine" + } + ], + "name": "locomotion", + "type": "state" + }, + { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "sitting-ground", + "type": "animation" + } + ], + "name": "sitting-ground", + "type": "state" + }, + { + "children": [ + { + "animationName": "sitting", + "type": "animation" + } + ], + "name": "sitting", + "type": "state" + } + ], + "name": "action", + "type": "stateMachine" + } + ], + "name": "action", + "type": "state" + } + ], + "name": "main", + "type": "stateMachine" + } + ], + "type": "output" + }, + "skeletonSource": "", + "version": 1 + }, + { + "name": "male1_5", + "root": { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "sitting-vehicle", + "type": "animation" + } + ], + "name": "driving", + "type": "state" + }, + { + "children": [ + { + "animationName": "idle", + "type": "animation" + } + ], + "name": "idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "walking", + "type": "animation" + } + ], + "name": "walking", + "type": "state" + }, + { + "children": [ + { + "animationName": "running", + "type": "animation" + } + ], + "name": "running", + "type": "state" + }, + { + "children": [ + { + "animationName": "treading_water", + "type": "animation" + } + ], + "name": "swim-idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "swimming", + "type": "animation" + } + ], + "name": "swimming", + "type": "state" + }, + { + "children": [ + { + "children": [ + { + "animationName": "swimming", + "type": "animation" + } + ], + "speed": 10.0, + "type": "speed" + } + ], + "name": "swimming-fast", + "type": "state" + } + ], + "name": "locomotion", + "type": "stateMachine" + } + ], + "name": "locomotion", + "type": "state" + }, + { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "sitting-ground", + "type": "animation" + } + ], + "name": "sitting-ground", + "type": "state" + }, + { + "children": [ + { + "animationName": "sitting", + "type": "animation" + } + ], + "name": "sitting", + "type": "state" + } + ], + "name": "action", + "type": "stateMachine" + } + ], + "name": "action", + "type": "state" + } + ], + "name": "main", + "type": "stateMachine" + } + ], + "type": "output" + }, + "skeletonSource": "", + "version": 1 + }, + { + "name": "male1_2", + "root": { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "sitting-vehicle", + "type": "animation" + } + ], + "name": "driving", + "type": "state" + }, + { + "children": [ + { + "animationName": "idle", + "type": "animation" + } + ], + "name": "idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "walking", + "type": "animation" + } + ], + "name": "walking", + "type": "state" + }, + { + "children": [ + { + "animationName": "running", + "type": "animation" + } + ], + "name": "running", + "type": "state" + }, + { + "children": [ + { + "animationName": "treading_water", + "type": "animation" + } + ], + "name": "swim-idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "swimming", + "type": "animation" + } + ], + "name": "swimming", + "type": "state" + }, + { + "children": [ + { + "children": [ + { + "animationName": "swimming", + "type": "animation" + } + ], + "speed": 10.0, + "type": "speed" + } + ], + "name": "swimming-fast", + "type": "state" + } + ], + "name": "locomotion", + "type": "stateMachine" + } + ], + "name": "locomotion", + "type": "state" + }, + { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "sitting-ground", + "type": "animation" + } + ], + "name": "sitting-ground", + "type": "state" + }, + { + "children": [ + { + "animationName": "sitting", + "type": "animation" + } + ], + "name": "sitting", + "type": "state" + } + ], + "name": "action", + "type": "stateMachine" + } + ], + "name": "action", + "type": "state" + } + ], + "name": "main", + "type": "stateMachine" + } + ], + "type": "output" + }, + "skeletonSource": "", + "version": 1 + }, + { + "name": "male1_1", + "root": { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "sitting-vehicle", + "type": "animation" + } + ], + "name": "driving", + "type": "state" + }, + { + "children": [ + { + "animationName": "idle", + "type": "animation" + } + ], + "name": "idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "walking", + "type": "animation" + } + ], + "name": "walking", + "type": "state" + }, + { + "children": [ + { + "animationName": "running", + "type": "animation" + } + ], + "name": "running", + "type": "state" + }, + { + "children": [ + { + "animationName": "treading_water", + "type": "animation" + } + ], + "name": "swim-idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "swimming", + "type": "animation" + } + ], + "name": "swimming", + "type": "state" + }, + { + "children": [ + { + "children": [ + { + "animationName": "swimming", + "type": "animation" + } + ], + "speed": 10.0, + "type": "speed" + } + ], + "name": "swimming-fast", + "type": "state" + } + ], + "name": "locomotion", + "type": "stateMachine" + } + ], + "name": "locomotion", + "type": "state" + }, + { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "sitting-ground", + "type": "animation" + } + ], + "name": "sitting-ground", + "type": "state" + }, + { + "children": [ + { + "animationName": "sitting", + "type": "animation" + } + ], + "name": "sitting", + "type": "state" + } + ], + "name": "action", + "type": "stateMachine" + } + ], + "name": "action", + "type": "state" + } + ], + "name": "main", + "type": "stateMachine" + } + ], + "type": "output" + }, + "skeletonSource": "", + "version": 1 + }, + { + "name": "male1", + "root": { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "sitting-vehicle", + "type": "animation" + } + ], + "name": "driving", + "type": "state" + }, + { + "children": [ + { + "animationName": "idle", + "type": "animation" + } + ], + "name": "idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "walking", + "type": "animation" + } + ], + "name": "walking", + "type": "state" + }, + { + "children": [ + { + "animationName": "running", + "type": "animation" + } + ], + "name": "running", + "type": "state" + }, + { + "children": [ + { + "animationName": "treading_water", + "type": "animation" + } + ], + "name": "swim-idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "swimming", + "type": "animation" + } + ], + "name": "swimming", + "type": "state" + }, + { + "children": [ + { + "children": [ + { + "animationName": "swimming", + "type": "animation" + } + ], + "speed": 10.0, + "type": "speed" + } + ], + "name": "swimming-fast", + "type": "state" + } + ], + "name": "locomotion", + "type": "stateMachine" + } + ], + "name": "locomotion", + "type": "state" + }, + { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "sitting-ground", + "type": "animation" + } + ], + "name": "sitting-ground", + "type": "state" + }, + { + "children": [ + { + "animationName": "sitting", + "type": "animation" + } + ], + "name": "sitting", + "type": "state" + } + ], + "name": "action", + "type": "stateMachine" + } + ], + "name": "action", + "type": "state" + } + ], + "name": "main", + "type": "stateMachine" + } + ], + "type": "output" + }, + "skeletonSource": "", + "version": 1 + }, + { + "name": "male1_3", + "root": { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "sitting-vehicle", + "type": "animation" + } + ], + "name": "driving", + "type": "state" + }, + { + "children": [ + { + "animationName": "idle", + "type": "animation" + } + ], + "name": "idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "walking", + "type": "animation" + } + ], + "name": "walking", + "type": "state" + }, + { + "children": [ + { + "animationName": "running", + "type": "animation" + } + ], + "name": "running", + "type": "state" + }, + { + "children": [ + { + "animationName": "treading_water", + "type": "animation" + } + ], + "name": "swim-idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "swimming", + "type": "animation" + } + ], + "name": "swimming", + "type": "state" + }, + { + "children": [ + { + "children": [ + { + "animationName": "swimming", + "type": "animation" + } + ], + "speed": 10.0, + "type": "speed" + } + ], + "name": "swimming-fast", + "type": "state" + } + ], + "name": "locomotion", + "type": "stateMachine" + } + ], + "name": "locomotion", + "type": "state" + }, + { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "sitting-ground", + "type": "animation" + } + ], + "name": "sitting-ground", + "type": "state" + }, + { + "children": [ + { + "animationName": "sitting", + "type": "animation" + } + ], + "name": "sitting", + "type": "state" + } + ], + "name": "action", + "type": "stateMachine" + } + ], + "name": "action", + "type": "state" + } + ], + "name": "main", + "type": "stateMachine" + } + ], + "type": "output" + }, + "skeletonSource": "", + "version": 1 + }, + { + "name": "male1_4", + "root": { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "sitting-vehicle", + "type": "animation" + } + ], + "name": "driving", + "type": "state" + }, + { + "children": [ + { + "animationName": "idle", + "type": "animation" + } + ], + "name": "idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "walking", + "type": "animation" + } + ], + "name": "walking", + "type": "state" + }, + { + "children": [ + { + "animationName": "running", + "type": "animation" + } + ], + "name": "running", + "type": "state" + }, + { + "children": [ + { + "animationName": "treading_water", + "type": "animation" + } + ], + "name": "swim-idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "swimming", + "type": "animation" + } + ], + "name": "swimming", + "type": "state" + }, + { + "children": [ + { + "children": [ + { + "animationName": "swimming", + "type": "animation" + } + ], + "speed": 10.0, + "type": "speed" + } + ], + "name": "swimming-fast", + "type": "state" + } + ], + "name": "locomotion", + "type": "stateMachine" + } + ], + "name": "locomotion", + "type": "state" + }, + { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "sitting-ground", + "type": "animation" + } + ], + "name": "sitting-ground", + "type": "state" + }, + { + "children": [ + { + "animationName": "sitting", + "type": "animation" + } + ], + "name": "sitting", + "type": "state" + } + ], + "name": "action", + "type": "stateMachine" + } + ], + "name": "action", + "type": "state" + } + ], + "name": "main", + "type": "stateMachine" + } + ], + "type": "output" + }, + "skeletonSource": "", + "version": 1 + }, + { + "name": "nav-test", + "root": { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "idle", + "type": "animation" + } + ], + "name": "idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "walking", + "type": "animation" + } + ], + "name": "walk", + "type": "state" + } + ], + "name": "locomotion", + "type": "stateMachine" + } + ], + "name": "locomotion", + "type": "state" + }, + { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "sitting-ground", + "type": "animation" + } + ], + "name": "sitting-ground", + "type": "state" + } + ], + "name": "action", + "type": "stateMachine" + } + ], + "name": "action", + "type": "state" + } + ], + "name": "main", + "type": "stateMachine" + } + ], + "type": "output" + }, + "version": 1 + }, + { + "name": "nav-test_1", + "root": { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "idle", + "type": "animation" + } + ], + "name": "idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "walking", + "type": "animation" + } + ], + "name": "walking", + "type": "state" + }, + { + "children": [ + { + "animationName": "running", + "type": "animation" + } + ], + "name": "running", + "type": "state" + }, + { + "children": [ + { + "animationName": "treading_water", + "type": "animation" + } + ], + "name": "swim-idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "swimming", + "type": "animation" + } + ], + "name": "swimming", + "type": "state" + }, + { + "children": [ + { + "children": [ + { + "animationName": "swimming", + "type": "animation" + } + ], + "speed": 10.0, + "type": "speed" + } + ], + "name": "swimming-fast", + "type": "state" + } + ], + "name": "locomotion", + "type": "stateMachine" + } + ], + "name": "locomotion", + "type": "state" + }, + { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "sitting-ground", + "type": "animation" + } + ], + "name": "sitting-ground", + "type": "state" + } + ], + "name": "action", + "type": "stateMachine" + } + ], + "name": "action", + "type": "state" + } + ], + "name": "main", + "type": "stateMachine" + } + ], + "type": "output" + }, + "version": 1 + }, + { + "name": "town7", + "root": { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "idle", + "type": "animation" + } + ], + "name": "idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "walking", + "type": "animation" + } + ], + "name": "walking", + "type": "state" + } + ], + "name": "main", + "type": "stateMachine" + } + ], + "type": "output" + }, + "version": 1 + }, + { + "name": "town7_1", + "root": { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "idle", + "type": "animation" + } + ], + "name": "idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "walking", + "type": "animation" + } + ], + "name": "walking", + "type": "state" + } + ], + "name": "idle", + "type": "stateMachine" + } + ], + "type": "output" + }, + "version": 1 + }, + { + "name": "town8a", + "root": { + "children": [ + { + "children": [ + { + "children": [ + { + "animationName": "idle", + "type": "animation" + } + ], + "name": "idle", + "type": "state" + }, + { + "children": [ + { + "animationName": "walking", + "type": "animation" + } + ], + "name": "walking", + "type": "state" + }, + { + "children": [ + { + "animationName": "running", + "type": "animation" + } + ], + "name": "running", + "type": "state" + } + ], + "name": "locomotion", + "type": "stateMachine" + } + ], + "type": "output" + }, + "version": 1 + } + ], + "version": "1.0" +} \ No newline at end of file diff --git a/src/features/editScene/demos/demo-taxi/demo_main.cpp b/src/features/editScene/demos/demo-taxi/demo_main.cpp index 51eb3b3..542342e 100644 --- a/src/features/editScene/demos/demo-taxi/demo_main.cpp +++ b/src/features/editScene/demos/demo-taxi/demo_main.cpp @@ -6,12 +6,22 @@ #include "systems/CharacterRegistry.hpp" #include "systems/DoorSystem.hpp" #include "systems/PlayerControllerSystem.hpp" +#include "systems/VehicleControllerSystem.hpp" +#include "systems/VehicleSystem.hpp" +#include "systems/CharacterSystem.hpp" +#include "systems/PhysicsSystem.hpp" #include "systems/TerrainSystem.hpp" #include "systems/EventBus.hpp" #include "systems/GlobalStateStore.hpp" #include "components/Door.hpp" #include "components/EntityName.hpp" #include "components/Transform.hpp" +#include "components/RigidBody.hpp" +#include "components/Vehicle.hpp" +#include "components/Character.hpp" +#include "components/PlayerController.hpp" +#include +#include #include #include #include @@ -46,6 +56,12 @@ struct ExitAfterFirstFrameListener : public Ogre::FrameListener { * --screenshot N path write frame N of the render window to * path (RenderWindow::writeContentsToFile) * and quit. + * --debug-enter-vehicle name + * send the vehicle_enter event for the named + * vehicle once the player + vehicle are up + * (visual tuning of seat offsets/camera with + * --screenshot); --debug-enter-seat picks the + * seat (default: driver). * --debug-buoyancy enable BuoyancySystem debug logging (same * as the editor's --debug-buoyancy). */ struct DebugCaptureListener : public Ogre::FrameListener { @@ -57,6 +73,9 @@ struct DebugCaptureListener : public Ogre::FrameListener { float teleportPitchDeg = 0.0f; int shotFrame = -1; Ogre::String shotFile; + Ogre::String enterVehicleName; + Ogre::String enterVehicleSeat; + bool enterSent = false; int frame = 0; DebugCaptureListener(EditorApp *a) : app(a) {} @@ -64,6 +83,87 @@ struct DebugCaptureListener : public Ogre::FrameListener { bool frameRenderingQueued(const Ogre::FrameEvent &) override { frame++; + if (!enterVehicleName.empty() && !enterSent && frame > 60) { + flecs::entity player = app->getPlayerCharacterEntity(); + flecs::entity vehicle = flecs::entity::null(); + app->getWorld()->query().each( + [&](flecs::entity e, EntityNameComponent &n) { + if (n.name == enterVehicleName) + vehicle = e; + }); + if (player.is_alive() && vehicle.is_alive() && + vehicle.has() && + vehicle.get() + .constraintCreated) { + std::cout << "[demo] debug-enter-vehicle: sending " + "vehicle_enter for " + << enterVehicleName << " seat " + << enterVehicleSeat << std::endl; + editScene::EventParams params; + params.setString("vehicle", enterVehicleName); + params.setString("seat", enterVehicleSeat); + EventBus::getInstance().send("vehicle_enter", + params); + enterSent = true; + } + } + /* One-shot pose probe after --debug-enter-vehicle: character + * node and key bone positions (model space) to check the seat + * pin against the actual rendered pose. */ + if (enterSent && frame > 120 && frame % 60 == 0) { + flecs::entity player = app->getPlayerCharacterEntity(); + if (player.is_alive() && + player.has()) { + const TransformComponent &t = + player.get(); + if (t.node) { + std::cout << "[probe] char node " + << t.node->_getDerivedPosition() + << std::endl; + for (Ogre::MovableObject *m : + t.node->getAttachedObjects()) { + if (m->getMovableType() != "Entity") + continue; + Ogre::Entity *ent = + static_cast(m); + if (!ent->hasSkeleton()) + continue; + Ogre::SkeletonInstance *sk = + ent->getSkeleton(); + for (const char *bn : + {"Root", "mixamorig:Hips", + "mixamorig:Head"}) { + if (!sk->hasBone(bn)) + continue; + std::cout + << "[probe] bone " + << bn << " " + << sk->getBone(bn) + ->_getDerivedPosition() + << std::endl; + } + Ogre::AnimationStateSet *aset = + ent->getAllAnimationStates(); + if (!aset) + continue; + Ogre::ConstEnabledAnimationStateIterator ait = + aset->getEnabledAnimationStateIterator(); + while (ait.hasMoreElements()) { + Ogre::AnimationState *as2 = + ait.getNext(); + std::cout + << "[probe] anim " + << as2->getAnimationName() + << " w " + << as2->getWeight() + << " t " + << as2->getTimePosition() + << std::endl; + } + } + } + } + } if (teleportEnabled) { flecs::entity player = app->getPlayerCharacterEntity(); if (player.is_alive() && @@ -106,9 +206,9 @@ struct DebugCaptureListener : public Ogre::FrameListener { app->getPlayerControllerSystem()->setControllerYawPitch( teleportYawDeg, teleportPitchDeg); if (shotFrame > 0 && frame >= shotFrame) { - app->getRenderWindow()->writeContentsToFile(shotFile); - app->getRoot()->queueEndRendering(); - } + app->getRenderWindow()->writeContentsToFile(shotFile); + app->getRoot()->queueEndRendering(); + } return true; } }; @@ -137,7 +237,8 @@ public: */ static void createFloorMesh(const Ogre::String &meshName, const Ogre::String &materialName, - float dr, float dg, float db) + float dr, float dg, float db, + float size = 60.0f) { const Ogre::String group = Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME; @@ -155,20 +256,150 @@ static void createFloorMesh(const Ogre::String &meshName, pass->setSpecular(0.0f, 0.0f, 0.0f, 1.0f); /* 60 x 60 units, matching the 30 x 0.1 x 30 static box collider - * (top surface at y = 0) on the "demo_floor" entity in the scenes. */ + * (top surface at y = 0) on the "demo_floor" entity in the scenes. + * The vehicle test scene uses a 200 x 200 variant (sedans outrun + * the 60 x 60 floor during the drive test). */ Ogre::Plane groundPlane(Ogre::Vector3::UNIT_Y, 0.0f); Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().createPlane( - meshName, group, groundPlane, 60.0f, 60.0f, 1, 1, true, 1, + meshName, group, groundPlane, size, size, 1, 1, true, 1, 1.0f, 1.0f, Ogre::Vector3::UNIT_Z); mesh->getSubMesh(0)->setMaterialName(materialName); } +/* + * The exported glass materials are plain opaque, which hides the + * cabin (and the seated driver) completely. Turn each glass material + * alpha-blended (the near look) and clone an opaque "-opaque" + * variant; SedanGlassLodListener swaps each vehicle's glass submeshes + * between the two depending on camera distance (Ogre 14 no longer + * applies Material::setLodLevels to entities, so a real LOD material + * never switches technique). Done programmatically because the + * .material files are regenerated by every blender2ogre export. + * + * The list covers the procedural sedans ("sedan-glass") and the + * convention-built cars exported by gen_vehicle.py ("-glass"); + * extend it when adding car families. + */ +static const char *kGlassMaterialNames[] = {"sedan-glass", + "sedan-v2-glass"}; + +static void fixupSedanGlassMaterial() +{ + for (const char *name : kGlassMaterialNames) { + Ogre::MaterialPtr glass = + Ogre::MaterialManager::getSingleton().getByName(name); + if (glass.isNull()) + continue; + + /* Clone BEFORE turning the source transparent so the + * opaque variant keeps the exported shading. */ + const Ogre::String opaqueName = + Ogre::String(name) + "-opaque"; + if (Ogre::MaterialManager::getSingleton() + .getByName(opaqueName) + .isNull()) + glass->clone(opaqueName); + + Ogre::Pass *pass = glass->getTechnique(0)->getPass(0); + const Ogre::ColourValue diffuse = pass->getDiffuse(); + pass->setDiffuse(diffuse.r, diffuse.g, diffuse.b, 0.25f); + pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); + pass->setDepthWriteEnabled(false); + } +} + +/* + * Per-vehicle window-glass swap: the plain material (transparent) + * within 10 m of the camera, its "-opaque" clone beyond — see + * fixupSedanGlassMaterial. Walks the whole node subtree of each + * vehicle, so glass on child entities (the sedan-v2 doors carry their + * windows) is covered too. + */ +struct SedanGlassLodListener : public Ogre::FrameListener { + EditorApp *app; + int frame = 0; + + SedanGlassLodListener(EditorApp *a) : app(a) {} + + bool frameRenderingQueued(const Ogre::FrameEvent &) override + { + if ((frame++ & 7) != 0) + return true; + EditorCamera *ec = app->getEditorCamera(); + Ogre::Camera *cam = ec ? ec->getCamera() : nullptr; + Ogre::SceneNode *camNode = + cam ? cam->getParentSceneNode() : nullptr; + if (!camNode) + return true; + const Ogre::Vector3 camPos = camNode->_getDerivedPosition(); + app->getWorld() + ->query() + .each([&](flecs::entity, TransformComponent &t, + VehicleComponent &) { + if (!t.node) + return; + const bool near = + t.node->_getDerivedPosition() + .squaredDistance(camPos) < + 100.0f; + std::vector stack; + stack.push_back(t.node); + while (!stack.empty()) { + Ogre::SceneNode *cur = stack.back(); + stack.pop_back(); + for (Ogre::MovableObject *m : + cur->getAttachedObjects()) { + if (m->getMovableType() != "Entity") + continue; + Ogre::Entity *ent = + static_cast(m); + for (unsigned int i = 0; + i < ent->getNumSubEntities(); + i++) { + Ogre::SubEntity *sub = + ent->getSubEntity(i); + const Ogre::String cur2 = + sub->getMaterialName(); + for (const char *prefix : + kGlassMaterialNames) { + if (cur2.find(prefix) != + 0) + continue; + const Ogre::String + want = + near ? + prefix : + Ogre::String(prefix) + + "-opaque"; + if (cur2 != want) + sub->setMaterialName( + want); + break; + } + } + } + for (Ogre::Node *c : + cur->getChildren()) + stack.push_back( + static_cast(c)); + } + }); + return true; + } +}; + static void createDemoResources() { /* Interior scene: green floor. The exterior scene has no floor * mesh — the terrain provides the ground. */ createFloorMesh("DemoFloorPlaneInterior", "DemoFloorMaterialInterior", 0.35f, 0.5f, 0.35f); + /* Vehicle test scene: bigger floor (matching 100 x 0.1 x 100 + * collider) so the sedan drive test stays on it. */ + createFloorMesh("DemoFloorPlaneVehicleTest", + "DemoFloorMaterialVehicleTest", + 0.35f, 0.5f, 0.35f, 200.0f); + fixupSedanGlassMaterial(); } /* @@ -707,6 +938,836 @@ struct SceneSwitchTestListener : public Ogre::FrameListener { } }; +/* + * Phase 1 end-to-end check for --test-vehicle-drive: loads the exterior + * scene (or the flat-floor demo_scene_vehicletest.json), waits for the + * sedan's vehicle constraint and the player character, then sends the + * "vehicle_enter" event (the same event the sedan door actuators' + * vehicle_seat_* actions send through the vehicleEnter Lua node), + * verifies the seated state, drives forward by holding the game-mode W + * input, verifies the steering wheel visual spins with D, exits via + * VehicleControllerSystem::exitVehicle() and verifies the character is + * back on foot next to the sedan, then repeats the enter on the + * passenger seat and verifies the vehicle is NOT player-controlled and + * does not move with W held. Ported from demo-sokoban's forklift + * drive test, parameterized by vehicle name ("sedan_taxi" in the + * exterior scene, "sedan1" in the vehicletest scene). + */ +struct VehicleDriveTestListener : public Ogre::FrameListener { + EditorApp *app; + Ogre::String vehicleName = "sedan_taxi"; + int frame = 0; + /* 0: wait spawn, 1: wait settle, 2: enter + drive, 3: W drive, + * 5: steering visual spin, 6: exit check, 7: passenger-seat enter + * (no player control), 8: passenger W no-drive + exit on the + * seat's side; 4: done */ + int phase = 0; + bool failed = false; + Ogre::String failReason; + Ogre::Vector3 startPos = Ogre::Vector3::ZERO; + Ogre::Vector3 authoredPos = Ogre::Vector3::ZERO; + bool authoredValid = false; + float lastY = 0.0f; + int settleFrames = 0; + int driveStart = 0; + bool hadCapsule = false; + Ogre::Quaternion steeringRest = Ogre::Quaternion::IDENTITY; + + VehicleDriveTestListener(EditorApp *a) + : app(a) + { + } + + flecs::entity findVehicle() + { + flecs::entity result = flecs::entity::null(); + app->getWorld()->query().each( + [&](flecs::entity e, EntityNameComponent &n) { + if (n.name == vehicleName) + result = e; + }); + return result; + } + + bool frameRenderingQueued(const Ogre::FrameEvent &) override + { + frame++; + if (failed || phase == 4) { + app->getRoot()->queueEndRendering(); + return true; + } + if (frame > 40000) { + failed = true; + failReason = "timeout in vehicle drive test"; + app->getRoot()->queueEndRendering(); + return true; + } + + flecs::entity sedan = findVehicle(); + if (!sedan.is_alive()) { + if (frame > 600) { + failed = true; + failReason = vehicleName + " entity not found"; + } + return true; + } + + /* Grounding watchdog: re-drop the chassis at its authored + * pose until the streaming terrain colliders catch it. */ + if (phase <= 1 && sedan.has() && + sedan.has()) { + const RigidBodyComponent &rb = + sedan.get(); + const TransformComponent &t = + sedan.get(); + if (!authoredValid) { + authoredPos = t.position; + authoredValid = true; + } + if (rb.bodyCreated && t.node) { + TerrainSystem *ts = TerrainSystem::getInstance(); + if (ts && ts->isActive()) { + Ogre::Vector3 pos = + t.node->_getDerivedPosition(); + float ground = ts->getHeightAt(pos); + float stray = Ogre::Vector2( + pos.x - authoredPos.x, + pos.z - authoredPos.z) + .length(); + if (pos.y < ground - 0.5f || stray > 2.0f) { + JoltPhysicsWrapper *phys = + app->getEditorPhysicsSystem() + ->getPhysicsWrapper(); + phys->setPositionAndRotation( + rb.bodyID, + Ogre::Vector3( + authoredPos.x, + ground + 1.0f, + authoredPos.z), + Ogre::Quaternion::IDENTITY); + phys->getPhysicsSystem() + ->GetBodyInterface() + .SetLinearAndAngularVelocity( + rb.bodyID, + JPH::Vec3::sZero(), + JPH::Vec3::sZero()); + settleFrames = 0; + lastY = ground + 1.0f; + return true; + } + } + } + } + + VehicleControllerSystem *vcs = app->getVehicleControllerSystem(); + if (!vcs) { + failed = true; + failReason = "no VehicleControllerSystem"; + return true; + } + + switch (phase) { + case 0: { + /* Wait for the chassis body + vehicle constraint and + * the player character. */ + if (!sedan.has() || + !sedan.has()) + break; + const RigidBodyComponent &rb = + sedan.get(); + const VehicleComponent &v = + sedan.get(); + if (!rb.bodyCreated || !v.constraintCreated) + break; + flecs::entity player = app->getPlayerCharacterEntity(); + if (!player.is_alive()) + break; + const TransformComponent &t = + sedan.get(); + startPos = t.node ? t.node->_getDerivedPosition() : + t.position; + lastY = startPos.y; + std::cout << "[test] drive-test: " << vehicleName + << " + player ready at (" + << startPos.x << ", " << startPos.y << ", " + << startPos.z << ")" << std::endl; + phase = 1; + break; + } + case 1: { + /* Wait until the chassis rests on the terrain. */ + const TransformComponent &t = + sedan.get(); + const RigidBodyComponent &rb = + sedan.get(); + JoltPhysicsWrapper *phys = app->getEditorPhysicsSystem() + ->getPhysicsWrapper(); + float speed = + phys->getLinearVelocity(rb.bodyID).length(); + Ogre::Quaternion q = t.node ? + t.node->_getDerivedOrientation() : + t.rotation; + float upY = (q * Ogre::Vector3::UNIT_Y).y; + float y = t.node ? t.node->_getDerivedPosition().y : + t.position.y; + if (speed < 0.1f && upY > 0.95f && + fabs(y - lastY) < 0.01f) { + if (++settleFrames > 60) { + startPos = + t.node ? + t.node->_getDerivedPosition() : + t.position; + std::cout << "[test] drive-test: " + << vehicleName << " settled" + << std::endl; + phase = 2; + } + } else { + settleFrames = 0; + } + lastY = y; + break; + } + case 2: { + /* Enter through the event (the actuator action path + * ends here too) and verify the seated state. */ + EventBus::getInstance().send("vehicle_enter", + "vehicle", vehicleName); + if (!vcs->isDriving()) { + failed = true; + failReason = "vehicle_enter event did not " + "start driving"; + break; + } + flecs::entity player = app->getPlayerCharacterEntity(); + if (!sedan.has()) { + failed = true; + failReason = vehicleName + + " missing " + "PlayerControlledComponent"; + break; + } + CharacterSystem *cs = app->getCharacterSystem(); + hadCapsule = cs && player.is_alive() && + cs->hasPhysicsCharacter(player); + if (hadCapsule && + !player.get() + .physicsDisabled) { + failed = true; + failReason = "character physics not " + "disabled while driving"; + break; + } + std::cout << "[test] drive-test: driving (capsule=" + << hadCapsule << ")" << std::endl; + + /* Rest orientation of the steering wheel visual (for + * the steering-spin check in phase 5). */ + VehicleSystem *vs = app->getVehicleSystem(); + Ogre::SceneNode *sw = + vs ? vs->getSteeringWheelNode(sedan.id()) : + nullptr; + if (sw) + steeringRest = sw->getOrientation(); + + /* Hold W like a player would. */ + app->getGameInputState().w = true; + driveStart = frame; + phase = 3; + break; + } + case 3: { + if (frame - driveStart < 240) + break; + app->getGameInputState().w = false; + const TransformComponent &t = + sedan.get(); + Ogre::Vector3 pos = t.node ? + t.node->_getDerivedPosition() : + t.position; + float moved = (pos - startPos).length(); + if (moved < 2.0f) { + failed = true; + failReason = + vehicleName + + " did not drive while " + "seated (moved " + + Ogre::StringConverter::toString( + moved) + + ")"; + break; + } + std::cout << "[test] drive-test: drove " << moved + << " units with W input" << std::endl; + + /* Hold D and verify the steering wheel visual + * spins (phase 5). */ + app->getGameInputState().d = true; + driveStart = frame; + phase = 5; + break; + } + case 5: { + if (frame - driveStart < 90) + break; + app->getGameInputState().d = false; + VehicleSystem *vs = app->getVehicleSystem(); + Ogre::SceneNode *sw = + vs ? vs->getSteeringWheelNode(sedan.id()) : + nullptr; + if (!sw) { + failed = true; + failReason = vehicleName + + " has no steering wheel " + "visual node"; + break; + } + float dot = fabsf(sw->getOrientation().Dot( + steeringRest)); + if (dot > 0.995f) { + failed = true; + failReason = + "steering wheel visual did not " + "spin with D input (dot " + + Ogre::StringConverter::toString(dot) + + ")"; + break; + } + std::cout << "[test] drive-test: steering wheel " + "visual spun with D input" + << std::endl; + driveStart = frame; + phase = 6; + break; + } + case 6: { + /* Let the steering settle back, then exit. */ + if (frame - driveStart < 60) + break; + const TransformComponent &ft = + sedan.get(); + Ogre::Vector3 pos = ft.node ? + ft.node->_getDerivedPosition() : + ft.position; + vcs->exitVehicle(); + if (vcs->isDriving()) { + failed = true; + failReason = "exitVehicle did not end " + "driving"; + break; + } + if (sedan.has()) { + failed = true; + failReason = vehicleName + + " still tagged " + "PlayerControlledComponent"; + break; + } + flecs::entity player = app->getPlayerCharacterEntity(); + if (!player.is_alive()) { + failed = true; + failReason = "player character gone after " + "exit"; + break; + } + if (hadCapsule && + player.get() + .physicsDisabled) { + failed = true; + failReason = "character physics not " + "re-enabled after exit"; + break; + } + const TransformComponent &pt = + player.get(); + Ogre::Vector3 ppos = + pt.node ? pt.node->_getDerivedPosition() : + pt.position; + float dist = (ppos - pos).length(); + if (dist > 4.0f) { + failed = true; + failReason = + "character too far from " + + vehicleName + + " after exit (" + + Ogre::StringConverter::toString( + dist) + + ")"; + break; + } + std::cout << "[test] drive-test: exited, character " + << dist << " units from " << vehicleName + << std::endl; + driveStart = frame; + phase = 7; + break; + } + case 7: { + /* Passenger seat: sit front-right; the vehicle must + * NOT become player-controlled. */ + if (frame - driveStart < 30) + break; + editScene::EventParams params; + params.setString("vehicle", vehicleName); + params.setString("seat", "passenger"); + EventBus::getInstance().send("vehicle_enter", params); + if (!vcs->isDriving()) { + failed = true; + failReason = "vehicle_enter (passenger seat) did " + "not seat the character"; + break; + } + if (sedan.has()) { + failed = true; + failReason = vehicleName + + " player-controlled from the " + "passenger seat"; + break; + } + flecs::entity player = app->getPlayerCharacterEntity(); + if (!player.is_alive()) { + failed = true; + failReason = "player character gone after " + "passenger enter"; + break; + } + /* The character should be pinned at the passenger + * seat, not the driver seat. */ + const TransformComponent &vt = + sedan.get(); + const VehicleComponent &v = + sedan.get(); + Ogre::Vector3 want; + for (const VehicleSeat &s : v.seats) + if (s.name == "passenger") + want = vt.node->_getDerivedOrientation() * + s.offset + + vt.node->_getDerivedPosition(); + const TransformComponent &pt = + player.get(); + Ogre::Vector3 ppos = + pt.node ? pt.node->_getDerivedPosition() : + pt.position; + if (want != Ogre::Vector3::ZERO && + ppos.distance(want) > 0.5f) { + failed = true; + failReason = "character not pinned at the " + "passenger seat"; + break; + } + const TransformComponent &t = + sedan.get(); + startPos = t.node ? t.node->_getDerivedPosition() : + t.position; + std::cout << "[test] drive-test: seated as passenger " + "(no player control)" + << std::endl; + /* Hold W: a passenger must not drive the car. */ + app->getGameInputState().w = true; + driveStart = frame; + phase = 8; + break; + } + case 8: { + if (frame - driveStart < 120) + break; + app->getGameInputState().w = false; + const TransformComponent &t = + sedan.get(); + Ogre::Vector3 pos = t.node ? + t.node->_getDerivedPosition() : + t.position; + float moved = (pos - startPos).length(); + if (moved > 1.0f) { + failed = true; + failReason = + vehicleName + + " moved with passenger-seat W input " + "(moved " + + Ogre::StringConverter::toString(moved) + + ")"; + break; + } + vcs->exitVehicle(); + if (vcs->isDriving()) { + failed = true; + failReason = "exitVehicle (passenger) did " + "not end the ride"; + break; + } + /* The exit spot must be on the side of the seat + * actually occupied (the passenger seat is on the + * right, chassis -X). */ + { + flecs::entity player = + app->getPlayerCharacterEntity(); + const VehicleComponent &vc = + sedan.get(); + const TransformComponent &vt = + sedan.get(); + float seatX = 0.0f; + for (const VehicleSeat &s : vc.seats) + if (s.name == "passenger") + seatX = s.offset.x; + if (player.is_alive() && + player.has() && vt.node && + seatX != 0.0f) { + const TransformComponent &pt = + player.get(); + Ogre::Vector3 cpos = + pt.node ? + pt.node->_getDerivedPosition() : + pt.position; + Ogre::Vector3 right = + vt.node->_getDerivedOrientation() * + Ogre::Vector3::UNIT_X; + float lat = (cpos - vt.node->_getDerivedPosition()) + .dotProduct(right); + if (lat * seatX <= 0.0f) { + failed = true; + failReason = + "exited on the wrong side " + "of the vehicle (lat " + + Ogre::StringConverter::toString( + lat) + + ", seat x " + + Ogre::StringConverter::toString( + seatX) + + ")"; + break; + } + std::cout << "[test] drive-test: " + "exited on the seat side " + "(lat " + << lat << ")" << std::endl; + } + } + std::cout << "[test] drive-test: passenger-seat W " + "input moved " + << moved << " units, exited" << std::endl; + std::cout << "[test] PASS" << std::endl; + phase = 4; + break; + } + } + return true; + } +}; + +/* + * --test-vehicle-turn: quantitative handling check on the flat + * vehicletest floor (sedan1). Enters the driver seat, holds W for + * 240 frames, then keeps W and adds full D lock for 150 frames while + * sampling the unwrapped heading. Prints speed at turn start, total + * yaw change and the deviation from the original straight-line + * heading; FAILs when the yaw change is implausibly small for the + * speed (the "drives like it's on ice" regression). + */ +struct VehicleTurnTestListener : public Ogre::FrameListener { + EditorApp *app; + Ogre::String vehicleName = "sedan1"; + int frame = 0; + /* 0: wait ready, 1: wait settle, 2: W drive, 3: W+D turn, + * 4: done */ + int phase = 0; + bool failed = false; + Ogre::String failReason; + int driveStart = 0; + float prevYaw = 0.0f; + float yawAccum = 0.0f; + float speedAtTurnStart = 0.0f; + float minUpY = 1.0f; + Ogre::Vector3 turnStartPos = Ogre::Vector3::ZERO; + Ogre::Vector3 turnStartDir = Ogre::Vector3::UNIT_Z; + + VehicleTurnTestListener(EditorApp *a) + : app(a) + { + } + + flecs::entity findVehicle() + { + flecs::entity result = flecs::entity::null(); + app->getWorld()->query().each( + [&](flecs::entity e, EntityNameComponent &n) { + if (n.name == vehicleName) + result = e; + }); + return result; + } + + static float headingYaw(flecs::entity v) + { + const TransformComponent &t = v.get(); + if (!t.node) + return 0.0f; + Ogre::Vector3 fwd = t.node->_getDerivedOrientation() * + Ogre::Vector3::UNIT_Z; + return atan2f(fwd.x, fwd.z); + } + + bool frameRenderingQueued(const Ogre::FrameEvent &) override + { + frame++; + if (failed || phase == 4) { + app->getRoot()->queueEndRendering(); + return true; + } + if (frame > 20000) { + failed = true; + failReason = "timeout in vehicle turn test"; + app->getRoot()->queueEndRendering(); + return true; + } + + flecs::entity sedan = findVehicle(); + if (!sedan.is_alive()) { + if (frame > 600) { + failed = true; + failReason = vehicleName + " entity not found"; + } + return true; + } + + switch (phase) { + case 0: { + if (!sedan.has() || + !sedan.has()) + break; + const RigidBodyComponent &rb = + sedan.get(); + const VehicleComponent &v = + sedan.get(); + if (!rb.bodyCreated || !v.constraintCreated) + break; + if (!app->getPlayerCharacterEntity().is_alive()) + break; + phase = 1; + break; + } + case 1: { + const RigidBodyComponent &rb = + sedan.get(); + JoltPhysicsWrapper *phys = + app->getEditorPhysicsSystem() + ->getPhysicsWrapper(); + if (phys->getLinearVelocity(rb.bodyID).length() < + 0.05f) { + EventBus::getInstance().send("vehicle_enter", + "vehicle", + vehicleName); + VehicleControllerSystem *vcs = + app->getVehicleControllerSystem(); + if (!vcs || !vcs->isDriving()) { + failed = true; + failReason = "vehicle_enter did not " + "start driving"; + break; + } + app->getGameInputState().w = true; + driveStart = frame; + phase = 2; + } + break; + } + case 2: { + if (frame - driveStart < 240) + break; + const RigidBodyComponent &rb = + sedan.get(); + JoltPhysicsWrapper *phys = + app->getEditorPhysicsSystem() + ->getPhysicsWrapper(); + speedAtTurnStart = + phys->getLinearVelocity(rb.bodyID).length(); + prevYaw = headingYaw(sedan); + const TransformComponent &t = + sedan.get(); + turnStartPos = t.node->_getDerivedPosition(); + turnStartDir = + t.node->_getDerivedOrientation() * + Ogre::Vector3::UNIT_Z; + turnStartDir.y = 0.0f; + turnStartDir.normalise(); + app->getGameInputState().d = true; + driveStart = frame; + phase = 3; + break; + } + case 3: { + float yaw = headingYaw(sedan); + float dy = yaw - prevYaw; + while (dy > Ogre::Math::PI) + dy -= 2.0f * Ogre::Math::PI; + while (dy < -Ogre::Math::PI) + dy += 2.0f * Ogre::Math::PI; + yawAccum += dy; + prevYaw = yaw; + { + const TransformComponent &tt = + sedan.get(); + float upY = (tt.node->_getDerivedOrientation() * + Ogre::Vector3::UNIT_Y) + .y; + minUpY = std::min(minUpY, upY); + } + if (frame - driveStart < 150) + break; + app->getGameInputState().w = false; + app->getGameInputState().d = false; + const TransformComponent &t = + sedan.get(); + Ogre::Vector3 movedXZ = + t.node->_getDerivedPosition() - turnStartPos; + movedXZ.y = 0.0f; + float along = movedXZ.dotProduct(turnStartDir); + float sideways = + movedXZ.crossProduct(turnStartDir).y; + float yawDeg = + Ogre::Math::RadiansToDegrees(yawAccum); + std::cout << "[test] turn-test: speed at turn " + "start " + << speedAtTurnStart << " m/s, yaw change " + << yawDeg << " deg over 150 frames, " + "travel " + << movedXZ.length() << " (along " << along + << ", sideways " << sideways + << "), max tilt " + << Ogre::Math::RadiansToDegrees( + acosf(std::max(-1.0f, minUpY))) + << " deg" << std::endl; + /* With ~1 g of lateral grip a car at >= 5 m/s turns + * far past 45 deg in 2.5 s at full lock; ice-like + * sliding stays in the teens. */ + if (speedAtTurnStart > 5.0f && fabsf(yawDeg) < 45.0f) { + failed = true; + failReason = + "car barely turns at speed (ice-like)"; + break; + } + std::cout << "[test] PASS" << std::endl; + phase = 4; + break; + } + } + return true; + } +}; + +/* + * --test-vehicle-righting: rolls sedan1 (vehicletest scene) 50 degrees + * onto two wheels and checks the VehicleSystem righting torque puts it + * back on four wheels within a few seconds (the "stuck at 45 deg on + * two wheels" case). Rolling fully onto the side/roof is NOT + * corrected by design. + */ +struct VehicleRightingTestListener : public Ogre::FrameListener { + EditorApp *app; + Ogre::String vehicleName = "sedan1"; + int frame = 0; + /* 0: wait ready, 1: rolled + measuring, 2: done */ + int phase = 0; + bool failed = false; + Ogre::String failReason; + int rollStart = 0; + + VehicleRightingTestListener(EditorApp *a) + : app(a) + { + } + + bool frameRenderingQueued(const Ogre::FrameEvent &) override + { + frame++; + if (failed || phase == 2) { + app->getRoot()->queueEndRendering(); + return true; + } + if (frame > 20000) { + failed = true; + failReason = "timeout in vehicle righting test"; + app->getRoot()->queueEndRendering(); + return true; + } + + flecs::entity sedan = flecs::entity::null(); + app->getWorld()->query().each( + [&](flecs::entity e, EntityNameComponent &n) { + if (n.name == vehicleName) + sedan = e; + }); + if (!sedan.is_alive()) { + if (frame > 600) { + failed = true; + failReason = vehicleName + " entity not found"; + } + return true; + } + if (!sedan.has() || + !sedan.has()) + return true; + const RigidBodyComponent &rb = + sedan.get(); + const TransformComponent &t = + sedan.get(); + if (!rb.bodyCreated || !t.node) + return true; + + JoltPhysicsWrapper *phys = + app->getEditorPhysicsSystem()->getPhysicsWrapper(); + + if (phase == 0) { + /* Wait until the chassis rests, then roll it 50 deg + * around its forward axis (onto two wheels). */ + if (phys->getLinearVelocity(rb.bodyID).length() > + 0.05f) + return true; + Ogre::Vector3 pos = t.node->_getDerivedPosition(); + Ogre::Quaternion rolled( + Ogre::Degree(50.0f), Ogre::Vector3::UNIT_Z); + phys->setPositionAndRotation( + rb.bodyID, + pos + Ogre::Vector3(0, 0.3f, 0), rolled); + phys->setVelocities(rb.bodyID, Ogre::Vector3::ZERO, + Ogre::Vector3::ZERO); + rollStart = frame; + std::cout << "[test] righting-test: rolled 50 deg" + << std::endl; + phase = 1; + return true; + } + + if (frame - rollStart > 600) { + Ogre::Vector3 up = + t.node->_getDerivedOrientation() * + Ogre::Vector3::UNIT_Y; + failed = true; + failReason = "car did not right itself within 600 " + "frames (final up.y " + + Ogre::StringConverter::toString(up.y) + + ", tilt " + + Ogre::StringConverter::toString( + Ogre::Math::RadiansToDegrees( + acosf(Ogre::Math::Clamp( + up.y, -1.0f, + 1.0f)))) + + " deg)"; + return true; + } + Ogre::Vector3 up = t.node->_getDerivedOrientation() * + Ogre::Vector3::UNIT_Y; + if (up.y > 0.99f) { + std::cout << "[test] righting-test: back on four " + "wheels after " + << (frame - rollStart) << " frames (up.y " + << up.y << ")" << std::endl; + std::cout << "[test] PASS" << std::endl; + phase = 2; + } + return true; + } +}; + /* * demo-taxi: taxi driver demo. Starts as an exact copy of * demos/demo-interior-exterior-dynamics (same scene-switch-door setup, @@ -790,6 +1851,25 @@ struct SceneSwitchTestListener : public Ogre::FrameListener { * controller, so each switch is a clean takeover (see * EditorApp::performSceneSwitch); travel back and forth is endless. * + * Phase 1 demo content: two drivable sedans parked near the content + * site in the exterior scene - "sedan_taxi" (sedan-taxi.mesh, taxi + * type) and "sedan_plain" (sedan-a-blue.mesh), both Jolt + * VehicleConstraint cars (front-wheel steering, rear-wheel drive, + * 1200 kg, maxTorque 300). Each sedan has 4 seats (VehicleComponent + * seats list: driver front-left, passenger front-right, rl/rr); each + * door child entity carries an actuator running one of the generic + * vehicle_seat_* actions (vehicleEnter Lua node -> "vehicle_enter" + * event with a seat param, vehicle resolved by proximity), so the + * player sits at the seat whose door they used - driving only works + * from the driver seat. The meshes come from + * assets/blender/scripts/gen_taxi_assets.py (sedan-a.blend / + * sedan-taxi.blend, exported like the other vehicles); the opaque + * exported window glass is made see-through near the camera and opaque + * past 10 m by a per-vehicle material swap at startup + per frame + * (fixupSedanGlassMaterial + SedanGlassLodListener). A flat-floor + * demo_scene_vehicletest.json ("sedan1", otherwise a copy of the + * sokoban vehicletest setup) hosts the headless drive test. + * * The mouse is grabbed while Playing (built-in game-mode behaviour); * Escape toggles the pause menu, which frees the cursor. * @@ -815,6 +1895,18 @@ struct SceneSwitchTestListener : public Ogre::FrameListener { * the expected Y from TerrainSystem::getHeightAt and * polls while the streaming window loads and the * grounding watchdog re-clamps); exits 0 on PASS. + * --test-vehicle-drive + * headless-friendly sedan enter/drive/exit check + * (Phase 1): loads the exterior scene directly (or the + * flat-floor demo_scene_vehicletest.json when given + * positionally), sends the "vehicle_enter" event (the + * same one the sedan actuator's vehicle_drive action + * sends through the vehicleEnter Lua node), drives by + * holding the game-mode W input, verifies the steering + * wheel visual spins with D, then exits via + * VehicleControllerSystem::exitVehicle() and checks the + * character is back on foot next to the sedan; exits 0 + * on PASS. */ int main(int argc, char *argv[]) { @@ -842,6 +1934,9 @@ int main(int argc, char *argv[]) bool headless = false; bool exitAfterFirstFrame = false; bool testSwitch = false; + bool testVehicleDrive = false; + bool testVehicleTurn = false; + bool testVehicleRighting = false; bool sceneArgGiven = false; Ogre::String shotFile; int shotFrame = -1; @@ -849,6 +1944,8 @@ int main(int argc, char *argv[]) Ogre::Vector3 forcePosVec = Ogre::Vector3::ZERO; float forcePosYaw = 0.0f; float forcePosPitch = 0.0f; + Ogre::String debugEnterVehicle; + Ogre::String debugEnterSeat; #ifdef EDITSCENE_HAS_EMBEDDED_PROJECT Ogre::String sceneFile = EDITSCENE_PROJECT_START_SCENE; #else @@ -862,8 +1959,21 @@ int main(int argc, char *argv[]) exitAfterFirstFrame = true; } else if (arg == "--test-switch") { testSwitch = true; + } else if (arg == "--test-vehicle-drive") { + testVehicleDrive = true; + } else if (arg == "--test-vehicle-turn") { + testVehicleTurn = true; + } else if (arg == "--test-vehicle-righting") { + testVehicleRighting = true; } else if (arg == "--debug-buoyancy") { app.setDebugBuoyancy(true); + } else if (arg == "--debug-enter-vehicle" && + i + 1 < argc) { + debugEnterVehicle = argv[i + 1]; + i += 1; + } else if (arg == "--debug-enter-seat" && i + 1 < argc) { + debugEnterSeat = argv[i + 1]; + i += 1; } else if (arg == "--screenshot" && i + 2 < argc) { shotFrame = atoi(argv[i + 1]); shotFile = argv[i + 2]; @@ -894,7 +2004,27 @@ int main(int argc, char *argv[]) sceneArgGiven = true; } } + /* The drive test drives the sedan parked in the exterior + * scene, so load that scene directly (unless the caller gave + * an explicit scene path, e.g. demo_scene_vehicletest.json). */ + if (testVehicleDrive && !sceneArgGiven) + sceneFile = "demo_scene_exterior.json"; + /* The turn test measures handling on the flat vehicletest + * floor. */ + if (testVehicleTurn && !sceneArgGiven) + sceneFile = "demo_scene_vehicletest.json"; + /* The righting test also runs on the flat vehicletest + * floor. */ + if (testVehicleRighting && !sceneArgGiven) + sceneFile = "demo_scene_vehicletest.json"; app.setHeadless(headless); + if (headless) { + /* Headless frames take ~1ms of wall time; pin a 60Hz + * frame delta so physics steps full fixed steps instead + * of tiny remainder slivers (soft vehicle suspension + * springs misbehave at millisecond deltas). */ + app.setFixedDeltaTime(1.0f / 60.0f); + } app.initApp(); @@ -922,17 +2052,40 @@ int main(int argc, char *argv[]) << sceneFile << std::endl; app.startNewGame(sceneFile); std::cout << "[demo] controls: mouse = look, W/A/S/D = move, " - "Shift = run, E = use door, Escape = pause menu" + "Shift = run, E = use door / drive a sedan, " + "hold E = exit the vehicle, Space = handbrake " + "while driving, Escape = pause menu" << std::endl; ExitAfterFirstFrameListener exitListener(app.getRoot()); - if (exitAfterFirstFrame && !testSwitch) + if (exitAfterFirstFrame && !testSwitch && !testVehicleDrive) app.getRoot()->addFrameListener(&exitListener); SceneSwitchTestListener testListener(&app); if (testSwitch) app.getRoot()->addFrameListener(&testListener); + VehicleDriveTestListener vehicleDriveListener(&app); + /* The exterior scene parks the taxi as "sedan_taxi"; the + * flat-floor vehicletest scene has "sedan1". */ + if (sceneFile.find("vehicletest") != Ogre::String::npos) + vehicleDriveListener.vehicleName = "sedan1"; + if (testVehicleDrive) + app.getRoot()->addFrameListener(&vehicleDriveListener); + + VehicleTurnTestListener vehicleTurnListener(&app); + if (testVehicleTurn) + app.getRoot()->addFrameListener(&vehicleTurnListener); + + VehicleRightingTestListener vehicleRightingListener(&app); + if (testVehicleRighting) + app.getRoot()->addFrameListener( + &vehicleRightingListener); + + /* Window glass near/far swap (always on). */ + SedanGlassLodListener glassListener(&app); + app.getRoot()->addFrameListener(&glassListener); + DebugCaptureListener captureListener(&app); if (forcePos) { captureListener.teleportEnabled = true; @@ -940,10 +2093,13 @@ int main(int argc, char *argv[]) captureListener.teleportYawDeg = forcePosYaw; captureListener.teleportPitchDeg = forcePosPitch; } + captureListener.enterVehicleName = debugEnterVehicle; + captureListener.enterVehicleSeat = debugEnterSeat; if (shotFrame > 0 && !shotFile.empty()) captureListener.shotFrame = shotFrame; captureListener.shotFile = shotFile; - if (forcePos || captureListener.shotFrame > 0) + if (forcePos || captureListener.shotFrame > 0 || + !debugEnterVehicle.empty()) app.getRoot()->addFrameListener(&captureListener); app.getRoot()->startRendering(); @@ -966,6 +2122,66 @@ int main(int argc, char *argv[]) } } + if (testVehicleDrive) { + if (vehicleDriveListener.failed) { + std::cerr << "[test] FAIL: " + << vehicleDriveListener.failReason + << std::endl; + app.clearScene(); + app.closeApp(); + return 1; + } + if (vehicleDriveListener.phase != 4) { + std::cerr << "[test] FAIL: incomplete (vehicle " + "drive phase " + << vehicleDriveListener.phase << ")" + << std::endl; + app.clearScene(); + app.closeApp(); + return 1; + } + } + + if (testVehicleTurn) { + if (vehicleTurnListener.failed) { + std::cerr << "[test] FAIL: " + << vehicleTurnListener.failReason + << std::endl; + app.clearScene(); + app.closeApp(); + return 1; + } + if (vehicleTurnListener.phase != 4) { + std::cerr << "[test] FAIL: incomplete (vehicle " + "turn phase " + << vehicleTurnListener.phase << ")" + << std::endl; + app.clearScene(); + app.closeApp(); + return 1; + } + } + + if (testVehicleRighting) { + if (vehicleRightingListener.failed) { + std::cerr << "[test] FAIL: " + << vehicleRightingListener.failReason + << std::endl; + app.clearScene(); + app.closeApp(); + return 1; + } + if (vehicleRightingListener.phase != 2) { + std::cerr << "[test] FAIL: incomplete (vehicle " + "righting phase " + << vehicleRightingListener.phase + << ")" << std::endl; + app.clearScene(); + app.closeApp(); + return 1; + } + } + /* Destroy scene entities while the systems are still alive: * CharacterSpawnerSystem registers an OnRemove observer on * CharacterSpawnerComponent that dereferences the system, and diff --git a/src/features/editScene/demos/demo-taxi/demo_scene_exterior.json b/src/features/editScene/demos/demo-taxi/demo_scene_exterior.json index a12a292..6887d92 100644 --- a/src/features/editScene/demos/demo-taxi/demo_scene_exterior.json +++ b/src/features/editScene/demos/demo-taxi/demo_scene_exterior.json @@ -1,771 +1,1634 @@ { - "actionDatabase": { - "actions": [ + "actionDatabase": { + "actions": [ + { + "behaviorTree": { + "children": [ { - "behaviorTree": { - "children": [ - { - "name": "luaHello", - "params": "message=Welcome to the game!", - "type": "luaTask" - }, - { - "name": "main/action", - "type": "setAnimationState" - }, - { - "name": "action/sitting-ground", - "type": "setAnimationState" - }, - { - "name": "dly", - "params": "9.0", - "type": "delay" - }, - { - "name": "main/locomotion", - "type": "setAnimationState" - }, - { - "name": "locomotion/idle", - "type": "setAnimationState" - } - ], - "type": "sequence" - }, - "cost": 1, - "effects": { - "bits": 0, - "mask": 0 - }, - "name": "lua_hello_action", - "preconditions": { - "bits": 0, - "mask": 0 - } + "name": "luaHello", + "params": "message=Welcome to the game!", + "type": "luaTask" }, { - "behaviorTree": { - "children": [ - { - "name": "main/action", - "type": "setAnimationState" - }, - { - "name": "action/sitting-ground", - "type": "setAnimationState" - }, - { - "name": "dly", - "params": "6.0", - "type": "delay" - }, - { - "name": "main/locomotion", - "type": "setAnimationState" - }, - { - "name": "locomotion/idle", - "type": "setAnimationState" - }, - { - "name": "luaHello", - "params": "message=\"hello, world!\"", - "type": "luaTask" - } - ], - "type": "sequence" - }, - "cost": 1, - "effects": { - "bits": 0, - "mask": 0 - }, - "name": "testAction", - "preconditions": { - "bits": 0, - "mask": 0 - } - } - ], - "bitNames": [ - { - "index": 1, - "name": "hungry" + "name": "main/action", + "type": "setAnimationState" }, { - "index": 2, - "name": "thirsty" - } - ], - "goals": [] - }, - "bookmarks": [], - "entities": [ - { - "children": [], - "id": 488, - "name": { - "name": "arrival_b" + "name": "action/sitting-ground", + "type": "setAnimationState" }, - "transform": { - "position": { - "x": 4000.60009765625, - "y": 9.990500450134277, - "z": 3995.89990234375 - }, - "rotation": { - "w": 0.0, - "x": 0.0, - "y": 1.0, - "z": 0.0 - }, - "scale": { - "x": 1.0, - "y": 1.0, - "z": 1.0 - } + { + "name": "dly", + "params": "9.0", + "type": "delay" + }, + { + "name": "main/locomotion", + "type": "setAnimationState" + }, + { + "name": "locomotion/idle", + "type": "setAnimationState" } + ], + "type": "sequence" }, - { - "children": [ - { - "cellGrid": { - "ceilingRectName": "", - "cellHeight": 4.0, - "cellSize": 2.0, - "cells": [ - { - "flags": 37757955, - "x": -1, - "y": 0, - "z": 0 - }, - { - "flags": 2097667, - "x": 0, - "y": 0, - "z": 0 - }, - { - "flags": 41953283, - "x": 1, - "y": 0, - "z": 0 - }, - { - "flags": 4195331, - "x": -1, - "y": 0, - "z": 1 - }, - { - "flags": 3, - "x": 0, - "y": 0, - "z": 1 - }, - { - "flags": 8390659, - "x": 1, - "y": 0, - "z": 1 - }, - { - "flags": 4195331, - "x": -1, - "y": 0, - "z": 2 - }, - { - "flags": 3, - "x": 0, - "y": 0, - "z": 2 - }, - { - "flags": 8390659, - "x": 1, - "y": 0, - "z": 2 - }, - { - "flags": 20976643, - "x": -1, - "y": 0, - "z": 3 - }, - { - "flags": 16781315, - "x": 0, - "y": 0, - "z": 3 - }, - { - "flags": 25171971, - "x": 1, - "y": 0, - "z": 3 - } - ], - "depth": 10, - "doorActionName": "", - "doorConfigs": {}, - "doorMeshName": "", - "doorOpenAngle": 100.0, - "doorOpenSpeed": 180.0, - "doorRectName": "", - "doorSceneSwitchPath": "demo_scene_interior.json", - "doorSceneSwitchTarget": "arrival_a", - "doorSwingReversed": false, - "doorUseMeshMaterial": false, - "doorsEnabled": true, - "extDoorFrameRectName": "", - "extWallRectName": "", - "extWindowFrameRectName": "", - "floorRectName": "", - "friction": 0.5, - "furnitureCells": [], - "generationMode": "exteriorOnly", - "generationScript": "", - "glassColor": [ - 0.4000000059604645, - 0.6000000238418579, - 0.800000011920929, - 0.3499999940395355 - ], - "glassMaterialName": "", - "glassReflectivity": 0.800000011920929, - "gridUid": "77370b1d-e8ab-44a6-865b-e55c15b0fc78", - "height": 1, - "intDoorFrameRectName": "", - "intWallRectName": "", - "intWindowFrameRectName": "", - "roofSideRectName": "", - "roofTopRectName": "", - "width": 10 - }, - "children": [ - { - "children": [], - "clearArea": { - "clearCells": true, - "clearFurniture": true, - "clearRoofs": false, - "clearRooms": false, - "maxX": 10, - "maxY": 1, - "maxZ": 10, - "minX": -10, - "minY": 0, - "minZ": -10 - }, - "id": 491, - "name": { - "name": "c1" - }, - "transform": { - "position": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "rotation": { - "w": 1.0, - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "scale": { - "x": 1.0, - "y": 1.0, - "z": 1.0 - } - } - }, - { - "children": [], - "id": 492, - "name": { - "name": "r1" - }, - "room": { - "connectedRoomIds": [], - "createCeiling": true, - "createFloor": true, - "createInteriorWalls": true, - "createWindows": true, - "exits": [ - true, - false, - false, - false - ], - "fillRoomWithFurniture": false, - "furnitureSeed": 42, - "furnitureYOffset": 0.05000000074505806, - "maxX": 2, - "maxY": 1, - "maxZ": 4, - "minX": -1, - "minY": 0, - "minZ": 0, - "persistentId": "room_1788689581735260047_499", - "roomType": "", - "tags": [] - }, - "transform": { - "position": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "rotation": { - "w": 1.0, - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "scale": { - "x": 1.0, - "y": 1.0, - "z": 1.0 - } - } - } - ], - "id": 490, - "name": { - "name": "w1" - }, - "transform": { - "position": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "rotation": { - "w": 1.0, - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "scale": { - "x": 1.0, - "y": 1.0, - "z": 1.0 - } - } - } - ], - "id": 489, - "name": { - "name": "x1" - }, - "transform": { - "position": { - "x": 4000.0, - "y": 10.014800071716309, - "z": 4000.0 - }, - "rotation": { - "w": 1.0, - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "scale": { - "x": 1.0, - "y": 1.0, - "z": 1.0 - } - } + "cost": 1, + "effects": { + "bits": 0, + "mask": 0 }, - { - "children": [], - "id": 493, - "light": { - "castShadows": false, - "constantAttenuation": 1.0, - "diffuseColor": { - "a": 1.0, - "b": 1.0, - "g": 1.0, - "r": 1.0 - }, - "direction": { - "x": 0.30000001192092896, - "y": -1.0, - "z": 0.20000000298023224 - }, - "intensity": 1.0, - "lightType": "directional", - "linearAttenuation": 0.0, - "quadraticAttenuation": 0.0, - "range": 100.0, - "specularColor": { - "a": 1.0, - "b": 0.5, - "g": 0.5, - "r": 0.5 - }, - "spotlightFalloff": 1.0, - "spotlightInnerAngle": 30.0, - "spotlightOuterAngle": 45.0 - }, - "name": { - "name": "demo_light" - }, - "transform": { - "position": { - "x": 4000.0, - "y": 20.0, - "z": 4000.0 - }, - "rotation": { - "w": 1.0, - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "scale": { - "x": 1.0, - "y": 1.0, - "z": 1.0 - } - } - }, - { - "characterSpawner": { - "despawnDistance": 200.0, - "registryId": 2, - "spawnDistance": 100.0 - }, - "children": [], - "id": 494, - "name": { - "name": "s1" - }, - "transform": { - "position": { - "x": 4000.0, - "y": 9.903900146484375, - "z": 3975.0 - }, - "rotation": { - "w": 1.0, - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "scale": { - "x": 1.0, - "y": 1.0, - "z": 1.0 - } - } - }, - { - "children": [], - "id": 495, - "name": { - "name": "player" - }, - "playerController": { - "actuatorColor": [ - 0.0, - 0.4000000059604645, - 1.0 - ], - "actuatorCooldown": 1.5, - "actuatorDistance": 25.0, - "actuatorLabelFontSize": 12.0, - "cameraMode": 0, - "distantCircleRadius": 8.0, - "fpsBoneName": "Head", - "idleState": "idle", - "locomotionStateMachine": "locomotion", - "mouseSensitivity": 0.20000000298023224, - "nearCircleRadius": 14.0, - "runState": "running", - "swimFastState": "swimming-fast", - "swimIdleState": "swim-idle", - "swimState": "swimming", - "targetCharacterName": "s1", - "tpsDistance": 3.0, - "tpsHeight": 2.0, - "walkState": "walking" - }, - "transform": { - "position": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "rotation": { - "w": 1.0, - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "scale": { - "x": 1.0, - "y": 1.0, - "z": 1.0 - } - } - }, - { - "children": [], - "id": 496, - "name": { - "name": "terrain" - }, - "terrain": { - "auxMaps": [], - "baseNoise": { - "amplitude": 15.0, - "frequency": 0.0003000000142492354, - "lacunarity": 2.0, - "octaves": 3, - "persistence": 0.5, - "seed": 55 - }, - "blendMapSize": 1024, - "compositeMapDistance": 300.0, - "detailNoise": { - "amplitude": 10.0, - "enabled": false, - "frequency": 0.006000000052154064, - "lacunarity": 2.0, - "octaves": 4, - "persistence": 0.5, - "seed": 26368 - }, - "enabled": true, - "farClipDistance": 6000.0, - "fogEnabled": true, - "fogEnd": 5500.0, - "fogStart": 2500.0, - "heightmapFile": "heightmap.bin", - "heightmapSize": 256, - "layers": [ - { - "diffuseTexture": "Ground23_col.jpg", - "name": "Base", - "normalTexture": "Ground23_normheight.dds", - "worldSize": 100.0 - }, - { - "diffuseTexture": "Ground37_diffspec.dds", - "name": "Layer 1", - "normalTexture": "Ground37_normheight.dds", - "worldSize": 100.0 - } - ], - "maxBatchSize": 65, - "maxPixelError": 1.0, - "minBatchSize": 17, - "pageHoldRadius": 3, - "pageLoadRadius": 2, - "roadConfig": { - "laneWidth": 3.0, - "lanesPerDirection": 1, - "prefabDespawnDistance": 250.0, - "prefabSpawnDistance": 150.0, - "roadLodDistance": 200.0, - "roadMaterialName": "RoadMaterial", - "roadMeshTemplate": "road_segment.mesh", - "roadThickness": 0.30000001192092896, - "roadVisibilityDistance": 1000.0, - "sidewalkEnabled": false, - "sidewalkHeight": 0.15000000596046448, - "sidewalkMeshTemplate": "", - "sidewalkThickness": 0.30000001192092896, - "sidewalkWidth": 1.5 - }, - "streamingEnabled": true, - "terrainId": 4242424300000001, - "terrainSize": 65, - "worldSize": 2000.0, - "worldSizeUnits": 10000.0 - }, - "transform": { - "position": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "rotation": { - "w": 1.0, - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "scale": { - "x": 1.0, - "y": 1.0, - "z": 1.0 - } - } - }, - { - "children": [], - "id": 497, - "name": { - "name": "sky" - }, - "skybox": { - "cloudiness": 0.0, - "dayBottomColor": [ - 0.6000000238418579, - 0.800000011920929, - 1.0 - ], - "dayTopColor": [ - 0.20000000298023224, - 0.5, - 1.0 - ], - "enabled": true, - "moonSize": 0.029999999329447746, - "nightBottomColor": [ - 0.05000000074505806, - 0.05000000074505806, - 0.15000000596046448 - ], - "nightTopColor": [ - 0.0, - 0.0, - 0.05000000074505806 - ], - "size": 443.0, - "starsEnabled": false, - "sunSize": 0.05000000074505806, - "sunriseColor": [ - 1.0, - 0.5, - 0.20000000298023224 - ], - "sunsetColor": [ - 1.0, - 0.30000001192092896, - 0.10000000149011612 - ] - }, - "sun": { - "ambientDay": [ - 0.30000001192092896, - 0.30000001192092896, - 0.30000001192092896 - ], - "ambientNight": [ - 0.05000000074505806, - 0.05000000074505806, - 0.15000000596046448 - ], - "ambientSunrise": [ - 0.30000001192092896, - 0.20000000298023224, - 0.15000000596046448 - ], - "ambientSunset": [ - 0.25, - 0.15000000596046448, - 0.10000000149011612 - ], - "castShadows": true, - "enabled": true, - "intensity": 1.7899999618530273, - "moonColor": [ - 0.30000001192092896, - 0.30000001192092896, - 0.5 - ], - "moonSphereSize": 3.4000000953674316, - "orbitTilt": 15.0, - "showMoonSphere": true, - "showSunSphere": true, - "sunColor": [ - 1.0, - 0.949999988079071, - 0.800000011920929 - ], - "sunSphereSize": 5.0, - "timeOfDay": 8.35942554473877, - "timeSpeed": 0.12999999523162842 - }, - "transform": { - "position": { - "x": 4000.0, - "y": 10.0, - "z": 4000.0 - }, - "rotation": { - "w": 1.0, - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "scale": { - "x": 1.0, - "y": 1.0, - "z": 1.0 - } - } - }, - { - "children": [], - "id": 498, - "name": { - "name": "water" - }, - "transform": { - "position": { - "x": 4000.0, - "y": 0.0, - "z": 4000.0 - }, - "rotation": { - "w": 1.0, - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "scale": { - "x": 1.0, - "y": 1.0, - "z": 1.0 - } - }, - "waterPhysics": { - "defaultAngularDrag": 0.05000000074505806, - "defaultBuoyancy": 1.0, - "defaultLinearDrag": 0.25, - "defaultSubmergedThreshold": 0.10000000149011612, - "enabled": true, - "gravity": 9.8100004196167, - "waterDensity": 1000.0, - "waterSurfaceY": 6.0 - }, - "waterPlane": { - "autoUpdateFromWaterPhysics": true, - "enabled": true, - "planeSize": 12000.0, - "reflectivity": 0.3799999952316284, - "renderTextureSize": 512, - "tiling": 0.012000000104308128, - "waterColor": [ - 0.0, - 0.30000001192092896, - 0.5, - 0.800000011920929 - ], - "waterSurfaceY": 6.0, - "waveScale": 0.03099999949336052, - "waveSpeed": 0.9800000190734863 - } + "name": "lua_hello_action", + "preconditions": { + "bits": 0, + "mask": 0 } + }, + { + "behaviorTree": { + "children": [ + { + "name": "main/action", + "type": "setAnimationState" + }, + { + "name": "action/sitting-ground", + "type": "setAnimationState" + }, + { + "name": "dly", + "params": "6.0", + "type": "delay" + }, + { + "name": "main/locomotion", + "type": "setAnimationState" + }, + { + "name": "locomotion/idle", + "type": "setAnimationState" + }, + { + "name": "luaHello", + "params": "message=\"hello, world!\"", + "type": "luaTask" + } + ], + "type": "sequence" + }, + "cost": 1, + "effects": { + "bits": 0, + "mask": 0 + }, + "name": "testAction", + "preconditions": { + "bits": 0, + "mask": 0 + } + }, + { + "behaviorTree": { + "children": [ + { + "name": "vehicleEnter", + "params": "seat=driver", + "type": "luaTask" + } + ], + "type": "sequence" + }, + "cost": 1, + "effects": { + "bits": 0, + "mask": 0 + }, + "name": "vehicle_seat_driver", + "preconditions": { + "bits": 0, + "mask": 0 + } + }, + { + "behaviorTree": { + "children": [ + { + "name": "vehicleEnter", + "params": "seat=passenger", + "type": "luaTask" + } + ], + "type": "sequence" + }, + "cost": 1, + "effects": { + "bits": 0, + "mask": 0 + }, + "name": "vehicle_seat_passenger", + "preconditions": { + "bits": 0, + "mask": 0 + } + }, + { + "behaviorTree": { + "children": [ + { + "name": "vehicleEnter", + "params": "seat=rl", + "type": "luaTask" + } + ], + "type": "sequence" + }, + "cost": 1, + "effects": { + "bits": 0, + "mask": 0 + }, + "name": "vehicle_seat_rl", + "preconditions": { + "bits": 0, + "mask": 0 + } + }, + { + "behaviorTree": { + "children": [ + { + "name": "vehicleEnter", + "params": "seat=rr", + "type": "luaTask" + } + ], + "type": "sequence" + }, + "cost": 1, + "effects": { + "bits": 0, + "mask": 0 + }, + "name": "vehicle_seat_rr", + "preconditions": { + "bits": 0, + "mask": 0 + } + } ], - "version": "1.0" -} \ No newline at end of file + "bitNames": [ + { + "index": 1, + "name": "hungry" + }, + { + "index": 2, + "name": "thirsty" + } + ], + "goals": [] + }, + "bookmarks": [], + "entities": [ + { + "children": [], + "id": 488, + "name": { + "name": "arrival_b" + }, + "transform": { + "position": { + "x": 4000.60009765625, + "y": 9.990500450134277, + "z": 3995.89990234375 + }, + "rotation": { + "w": 0.0, + "x": 0.0, + "y": 1.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "children": [ + { + "cellGrid": { + "ceilingRectName": "", + "cellHeight": 4.0, + "cellSize": 2.0, + "cells": [ + { + "flags": 37757955, + "x": -1, + "y": 0, + "z": 0 + }, + { + "flags": 2097667, + "x": 0, + "y": 0, + "z": 0 + }, + { + "flags": 41953283, + "x": 1, + "y": 0, + "z": 0 + }, + { + "flags": 4195331, + "x": -1, + "y": 0, + "z": 1 + }, + { + "flags": 3, + "x": 0, + "y": 0, + "z": 1 + }, + { + "flags": 8390659, + "x": 1, + "y": 0, + "z": 1 + }, + { + "flags": 4195331, + "x": -1, + "y": 0, + "z": 2 + }, + { + "flags": 3, + "x": 0, + "y": 0, + "z": 2 + }, + { + "flags": 8390659, + "x": 1, + "y": 0, + "z": 2 + }, + { + "flags": 20976643, + "x": -1, + "y": 0, + "z": 3 + }, + { + "flags": 16781315, + "x": 0, + "y": 0, + "z": 3 + }, + { + "flags": 25171971, + "x": 1, + "y": 0, + "z": 3 + } + ], + "depth": 10, + "doorActionName": "", + "doorConfigs": {}, + "doorMeshName": "", + "doorOpenAngle": 100.0, + "doorOpenSpeed": 180.0, + "doorRectName": "", + "doorSceneSwitchPath": "demo_scene_interior.json", + "doorSceneSwitchTarget": "arrival_a", + "doorSwingReversed": false, + "doorUseMeshMaterial": false, + "doorsEnabled": true, + "extDoorFrameRectName": "", + "extWallRectName": "", + "extWindowFrameRectName": "", + "floorRectName": "", + "friction": 0.5, + "furnitureCells": [], + "generationMode": "exteriorOnly", + "generationScript": "", + "glassColor": [ + 0.4000000059604645, + 0.6000000238418579, + 0.800000011920929, + 0.3499999940395355 + ], + "glassMaterialName": "", + "glassReflectivity": 0.800000011920929, + "gridUid": "77370b1d-e8ab-44a6-865b-e55c15b0fc78", + "height": 1, + "intDoorFrameRectName": "", + "intWallRectName": "", + "intWindowFrameRectName": "", + "roofSideRectName": "", + "roofTopRectName": "", + "width": 10 + }, + "children": [ + { + "children": [], + "clearArea": { + "clearCells": true, + "clearFurniture": true, + "clearRoofs": false, + "clearRooms": false, + "maxX": 10, + "maxY": 1, + "maxZ": 10, + "minX": -10, + "minY": 0, + "minZ": -10 + }, + "id": 491, + "name": { + "name": "c1" + }, + "transform": { + "position": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "children": [], + "id": 492, + "name": { + "name": "r1" + }, + "room": { + "connectedRoomIds": [], + "createCeiling": true, + "createFloor": true, + "createInteriorWalls": true, + "createWindows": true, + "exits": [ + true, + false, + false, + false + ], + "fillRoomWithFurniture": false, + "furnitureSeed": 42, + "furnitureYOffset": 0.05000000074505806, + "maxX": 2, + "maxY": 1, + "maxZ": 4, + "minX": -1, + "minY": 0, + "minZ": 0, + "persistentId": "room_1788689581735260047_499", + "roomType": "", + "tags": [] + }, + "transform": { + "position": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + } + ], + "id": 490, + "name": { + "name": "w1" + }, + "transform": { + "position": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + } + ], + "id": 489, + "name": { + "name": "x1" + }, + "transform": { + "position": { + "x": 4000.0, + "y": 10.014800071716309, + "z": 4000.0 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "children": [], + "id": 493, + "light": { + "castShadows": false, + "constantAttenuation": 1.0, + "diffuseColor": { + "a": 1.0, + "b": 1.0, + "g": 1.0, + "r": 1.0 + }, + "direction": { + "x": 0.30000001192092896, + "y": -1.0, + "z": 0.20000000298023224 + }, + "intensity": 1.0, + "lightType": "directional", + "linearAttenuation": 0.0, + "quadraticAttenuation": 0.0, + "range": 100.0, + "specularColor": { + "a": 1.0, + "b": 0.5, + "g": 0.5, + "r": 0.5 + }, + "spotlightFalloff": 1.0, + "spotlightInnerAngle": 30.0, + "spotlightOuterAngle": 45.0 + }, + "name": { + "name": "demo_light" + }, + "transform": { + "position": { + "x": 4000.0, + "y": 20.0, + "z": 4000.0 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "characterSpawner": { + "despawnDistance": 200.0, + "registryId": 2, + "spawnDistance": 100.0 + }, + "children": [], + "id": 494, + "name": { + "name": "s1" + }, + "transform": { + "position": { + "x": 4000.0, + "y": 9.903900146484375, + "z": 3975.0 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "children": [], + "id": 495, + "name": { + "name": "player" + }, + "playerController": { + "actuatorColor": [ + 0.0, + 0.4000000059604645, + 1.0 + ], + "actuatorCooldown": 1.5, + "actuatorDistance": 25.0, + "actuatorLabelFontSize": 12.0, + "cameraMode": 0, + "distantCircleRadius": 8.0, + "fpsBoneName": "Head", + "idleState": "idle", + "locomotionStateMachine": "locomotion", + "mouseSensitivity": 0.20000000298023224, + "nearCircleRadius": 14.0, + "runState": "running", + "swimFastState": "swimming-fast", + "swimIdleState": "swim-idle", + "swimState": "swimming", + "targetCharacterName": "s1", + "tpsDistance": 3.0, + "tpsHeight": 2.0, + "walkState": "walking" + }, + "transform": { + "position": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "children": [ + { + "actuator": { + "actionNames": [ + "vehicle_seat_driver" + ], + "height": 2.0, + "radius": 1.2 + }, + "children": [], + "id": 610, + "name": { + "name": "sedan_taxi_door_fl" + }, + "renderable": { + "meshName": "sedan-taxi-door-fl.mesh", + "visible": true + }, + "transform": { + "position": { + "x": 0.855, + "y": -0.1, + "z": 0.55 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "actuator": { + "actionNames": [ + "vehicle_seat_passenger" + ], + "height": 2.0, + "radius": 1.2 + }, + "children": [], + "id": 611, + "name": { + "name": "sedan_taxi_door_fr" + }, + "renderable": { + "meshName": "sedan-taxi-door-fr.mesh", + "visible": true + }, + "transform": { + "position": { + "x": -0.855, + "y": -0.1, + "z": 0.55 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "actuator": { + "actionNames": [ + "vehicle_seat_rl" + ], + "height": 2.0, + "radius": 1.2 + }, + "children": [], + "id": 612, + "name": { + "name": "sedan_taxi_door_rl" + }, + "renderable": { + "meshName": "sedan-taxi-door-rl.mesh", + "visible": true + }, + "transform": { + "position": { + "x": 0.855, + "y": -0.1, + "z": -0.46 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "actuator": { + "actionNames": [ + "vehicle_seat_rr" + ], + "height": 2.0, + "radius": 1.2 + }, + "children": [], + "id": 613, + "name": { + "name": "sedan_taxi_door_rr" + }, + "renderable": { + "meshName": "sedan-taxi-door-rr.mesh", + "visible": true + }, + "transform": { + "position": { + "x": -0.855, + "y": -0.1, + "z": -0.46 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "children": [], + "id": 614, + "name": { + "name": "sedan_taxi_hood" + }, + "renderable": { + "meshName": "sedan-taxi-hood.mesh", + "visible": true + }, + "transform": { + "position": { + "x": 0.0, + "y": 0.17, + "z": 0.75 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "children": [], + "id": 615, + "name": { + "name": "sedan_taxi_trunk" + }, + "renderable": { + "meshName": "sedan-taxi-trunk.mesh", + "visible": true + }, + "transform": { + "position": { + "x": 0.0, + "y": 0.27, + "z": -1.35 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + } + ], + "collider": { + "halfHeight": 1.0, + "meshName": "", + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "parameters": { + "x": 0.9, + "y": 0.35, + "z": 2.4 + }, + "radius": 0.5, + "rotationOffset": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "shapeType": "box" + }, + "id": 600, + "name": { + "name": "sedan_taxi" + }, + "renderable": { + "meshName": "sedan-taxi.mesh", + "visible": true + }, + "rigidBody": { + "bodyType": "dynamic", + "enabled": true, + "friction": 0.8, + "isSensor": false, + "mass": 1200.0, + "restitution": 0.0 + }, + "transform": { + "position": { + "x": 4006.0, + "y": 10.8, + "z": 3965.0 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + }, + "vehicle": { + "cameraDistance": 6.0, + "cameraHeight": 1.6, + "engineBrake": 0.5, + "maxPitchRollAngleDeg": 60.0, + "maxTorque": 300.0, + "rearSteer": false, + "seatOffset": { + "x": -0.35, + "y": 0.15, + "z": -0.1 + }, + "steeringRatio": 4.0, + "steeringWheelMeshName": "sedan-steering-wheel.mesh", + "steeringWheelOffset": { + "x": 0.35, + "y": 0.45, + "z": 0.2 + }, + "steeringWheelTiltDeg": 25.0, + "wheelMeshName": "sedan-wheel.mesh", + "wheels": [ + { + "driven": false, + "maxHandBrakeTorque": 0.0, + "maxSteerAngleDeg": 35.0, + "position": { + "x": -0.8, + "y": -0.25, + "z": 1.5 + }, + "radius": 0.33, + "suspensionDamping": 0.7, + "suspensionFrequency": 1.8, + "suspensionMaxLength": 0.35, + "suspensionMinLength": 0.1, + "width": 0.22 + }, + { + "driven": false, + "maxHandBrakeTorque": 0.0, + "maxSteerAngleDeg": 35.0, + "position": { + "x": 0.8, + "y": -0.25, + "z": 1.5 + }, + "radius": 0.33, + "suspensionDamping": 0.7, + "suspensionFrequency": 1.8, + "suspensionMaxLength": 0.35, + "suspensionMinLength": 0.1, + "width": 0.22 + }, + { + "driven": true, + "maxHandBrakeTorque": 300.0, + "maxSteerAngleDeg": 0.0, + "position": { + "x": -0.8, + "y": -0.25, + "z": -1.5 + }, + "radius": 0.33, + "suspensionDamping": 0.7, + "suspensionFrequency": 1.8, + "suspensionMaxLength": 0.35, + "suspensionMinLength": 0.1, + "width": 0.22 + }, + { + "driven": true, + "maxHandBrakeTorque": 300.0, + "maxSteerAngleDeg": 0.0, + "position": { + "x": 0.8, + "y": -0.25, + "z": -1.5 + }, + "radius": 0.33, + "suspensionDamping": 0.7, + "suspensionFrequency": 1.8, + "suspensionMaxLength": 0.35, + "suspensionMinLength": 0.1, + "width": 0.22 + } + ], + "seats": [ + { + "name": "driver", + "offset": { + "x": 0.35, + "y": -0.22, + "z": 0.25 + }, + "driver": true + }, + { + "name": "passenger", + "offset": { + "x": -0.35, + "y": -0.22, + "z": 0.25 + }, + "driver": false + }, + { + "name": "rl", + "offset": { + "x": 0.35, + "y": -0.22, + "z": -0.35 + }, + "driver": false + }, + { + "name": "rr", + "offset": { + "x": -0.35, + "y": -0.22, + "z": -0.35 + }, + "driver": false + } + ], + "seatCharacterScale": 0.85 + } + }, + { + "children": [ + { + "children": [], + "name": { + "name": "sedan_plain_door_rl" + }, + "renderable": { + "meshName": "sedan-v2-door-rl.mesh", + "visible": true + }, + "transform": { + "position": { + "x": 0.9192, + "y": -0.2869, + "z": -0.132 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + }, + "actuator": { + "actionNames": [ + "vehicle_seat_rl" + ], + "height": 2.0, + "radius": 1.2 + } + }, + { + "children": [], + "name": { + "name": "sedan_plain_door_rr" + }, + "renderable": { + "meshName": "sedan-v2-door-rr.mesh", + "visible": true + }, + "transform": { + "position": { + "x": -0.9192, + "y": -0.2869, + "z": -0.132 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + }, + "actuator": { + "actionNames": [ + "vehicle_seat_rr" + ], + "height": 2.0, + "radius": 1.2 + } + }, + { + "children": [], + "name": { + "name": "sedan_plain_door_fl" + }, + "renderable": { + "meshName": "sedan-v2-door-fl.mesh", + "visible": true + }, + "transform": { + "position": { + "x": 0.9192, + "y": -0.2868, + "z": 0.801 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + }, + "actuator": { + "actionNames": [ + "vehicle_seat_driver" + ], + "height": 2.0, + "radius": 1.2 + } + }, + { + "children": [], + "name": { + "name": "sedan_plain_door_fr" + }, + "renderable": { + "meshName": "sedan-v2-door-fr.mesh", + "visible": true + }, + "transform": { + "position": { + "x": -0.9192, + "y": -0.2868, + "z": 0.801 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + }, + "actuator": { + "actionNames": [ + "vehicle_seat_passenger" + ], + "height": 2.0, + "radius": 1.2 + } + }, + { + "children": [], + "name": { + "name": "sedan_plain_hood" + }, + "renderable": { + "meshName": "sedan-v2-hood.mesh", + "visible": true + }, + "transform": { + "position": { + "x": -0.0, + "y": 0.266, + "z": 0.9992 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "children": [], + "name": { + "name": "sedan_plain_trunk" + }, + "renderable": { + "meshName": "sedan-v2-trunk.mesh", + "visible": true + }, + "transform": { + "position": { + "x": 0.0, + "y": 0.2744, + "z": -1.446 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + } + ], + "collider": { + "halfHeight": 1.0, + "meshName": "", + "offset": { + "x": 0.0, + "y": 0.1535, + "z": -0.0629 + }, + "parameters": { + "x": 1.0107, + "y": 0.6126, + "z": 2.4834 + }, + "radius": 0.5, + "rotationOffset": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "shapeType": "box" + }, + "name": { + "name": "sedan_plain" + }, + "renderable": { + "meshName": "sedan-v2.mesh", + "visible": true + }, + "rigidBody": { + "bodyType": "dynamic", + "enabled": true, + "friction": 0.8, + "isSensor": false, + "mass": 1200.0, + "restitution": 0.0 + }, + "transform": { + "position": { + "x": 4018.0, + "y": 10.8, + "z": 3965.0 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + }, + "vehicle": { + "cameraDistance": 6.0, + "cameraHeight": 1.6, + "engineBrake": 0.5, + "maxPitchRollAngleDeg": 60.0, + "maxTorque": 300.0, + "rearSteer": false, + "seatOffset": { + "x": 0.48, + "y": -0.28, + "z": 0.2012 + }, + "steeringRatio": 4.0, + "steeringWheelMeshName": "sedan-v2-steering-wheel.mesh", + "steeringWheelOffset": { + "x": 0.4915, + "y": 0.0807, + "z": 0.601 + }, + "steeringWheelTiltDeg": 25.0, + "wheelMeshName": "sedan-v2-wheel.mesh", + "wheels": [ + { + "driven": false, + "maxHandBrakeTorque": 0.0, + "maxSteerAngleDeg": 35.0, + "position": { + "x": 0.7805, + "y": -0.2052, + "z": 1.4806 + }, + "radius": 0.3918, + "suspensionDamping": 0.6, + "suspensionFrequency": 1.3, + "suspensionMaxLength": 0.35, + "suspensionMinLength": 0.1, + "width": 0.3145 + }, + { + "driven": false, + "maxHandBrakeTorque": 0.0, + "maxSteerAngleDeg": 35.0, + "position": { + "x": -0.7805, + "y": -0.2052, + "z": 1.4806 + }, + "radius": 0.3918, + "suspensionDamping": 0.6, + "suspensionFrequency": 1.3, + "suspensionMaxLength": 0.35, + "suspensionMinLength": 0.1, + "width": 0.3145 + }, + { + "driven": true, + "maxHandBrakeTorque": 300.0, + "maxSteerAngleDeg": 0.0, + "position": { + "x": 0.7805, + "y": -0.2052, + "z": -1.2806 + }, + "radius": 0.3918, + "suspensionDamping": 0.6, + "suspensionFrequency": 1.3, + "suspensionMaxLength": 0.35, + "suspensionMinLength": 0.1, + "width": 0.3145 + }, + { + "driven": true, + "maxHandBrakeTorque": 300.0, + "maxSteerAngleDeg": 0.0, + "position": { + "x": -0.7805, + "y": -0.2052, + "z": -1.2806 + }, + "radius": 0.3918, + "suspensionDamping": 0.6, + "suspensionFrequency": 1.3, + "suspensionMaxLength": 0.35, + "suspensionMinLength": 0.1, + "width": 0.3145 + } + ], + "seats": [ + { + "name": "driver", + "offset": { + "x": 0.48, + "y": -0.28, + "z": 0.2012 + }, + "driver": true + }, + { + "name": "passenger", + "offset": { + "x": -0.48, + "y": -0.28, + "z": 0.2012 + }, + "driver": false + }, + { + "name": "rl", + "offset": { + "x": 0.48, + "y": -0.28, + "z": -0.6032 + }, + "driver": false + }, + { + "name": "rr", + "offset": { + "x": -0.48, + "y": -0.28, + "z": -0.6032 + }, + "driver": false + } + ], + "seatCharacterScale": 1.0, + "tireFrictionLongitudinal": 1.3, + "tireFrictionLateral": 3.0, + "antiRollBarStiffness": 2000.0 + }, + "id": 601 + }, + { + "children": [], + "id": 496, + "name": { + "name": "terrain" + }, + "terrain": { + "auxMaps": [], + "baseNoise": { + "amplitude": 15.0, + "frequency": 0.0003000000142492354, + "lacunarity": 2.0, + "octaves": 3, + "persistence": 0.5, + "seed": 55 + }, + "blendMapSize": 1024, + "compositeMapDistance": 300.0, + "detailNoise": { + "amplitude": 10.0, + "enabled": false, + "frequency": 0.006000000052154064, + "lacunarity": 2.0, + "octaves": 4, + "persistence": 0.5, + "seed": 26368 + }, + "enabled": true, + "farClipDistance": 6000.0, + "fogEnabled": true, + "fogEnd": 5500.0, + "fogStart": 2500.0, + "heightmapFile": "heightmap.bin", + "heightmapSize": 256, + "layers": [ + { + "diffuseTexture": "Ground23_col.jpg", + "name": "Base", + "normalTexture": "Ground23_normheight.dds", + "worldSize": 100.0 + }, + { + "diffuseTexture": "Ground37_diffspec.dds", + "name": "Layer 1", + "normalTexture": "Ground37_normheight.dds", + "worldSize": 100.0 + } + ], + "maxBatchSize": 65, + "maxPixelError": 1.0, + "minBatchSize": 17, + "pageHoldRadius": 3, + "pageLoadRadius": 2, + "roadConfig": { + "laneWidth": 3.0, + "lanesPerDirection": 1, + "prefabDespawnDistance": 250.0, + "prefabSpawnDistance": 150.0, + "roadLodDistance": 200.0, + "roadMaterialName": "RoadMaterial", + "roadMeshTemplate": "road_segment.mesh", + "roadThickness": 0.30000001192092896, + "roadVisibilityDistance": 1000.0, + "sidewalkEnabled": false, + "sidewalkHeight": 0.15000000596046448, + "sidewalkMeshTemplate": "", + "sidewalkThickness": 0.30000001192092896, + "sidewalkWidth": 1.5 + }, + "streamingEnabled": true, + "terrainId": 4242424300000001, + "terrainSize": 65, + "worldSize": 2000.0, + "worldSizeUnits": 10000.0 + }, + "transform": { + "position": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "children": [], + "id": 497, + "name": { + "name": "sky" + }, + "skybox": { + "cloudiness": 0.0, + "dayBottomColor": [ + 0.6000000238418579, + 0.800000011920929, + 1.0 + ], + "dayTopColor": [ + 0.20000000298023224, + 0.5, + 1.0 + ], + "enabled": true, + "moonSize": 0.029999999329447746, + "nightBottomColor": [ + 0.05000000074505806, + 0.05000000074505806, + 0.15000000596046448 + ], + "nightTopColor": [ + 0.0, + 0.0, + 0.05000000074505806 + ], + "size": 443.0, + "starsEnabled": false, + "sunSize": 0.05000000074505806, + "sunriseColor": [ + 1.0, + 0.5, + 0.20000000298023224 + ], + "sunsetColor": [ + 1.0, + 0.30000001192092896, + 0.10000000149011612 + ] + }, + "sun": { + "ambientDay": [ + 0.30000001192092896, + 0.30000001192092896, + 0.30000001192092896 + ], + "ambientNight": [ + 0.05000000074505806, + 0.05000000074505806, + 0.15000000596046448 + ], + "ambientSunrise": [ + 0.30000001192092896, + 0.20000000298023224, + 0.15000000596046448 + ], + "ambientSunset": [ + 0.25, + 0.15000000596046448, + 0.10000000149011612 + ], + "castShadows": true, + "enabled": true, + "intensity": 1.7899999618530273, + "moonColor": [ + 0.30000001192092896, + 0.30000001192092896, + 0.5 + ], + "moonSphereSize": 3.4000000953674316, + "orbitTilt": 15.0, + "showMoonSphere": true, + "showSunSphere": true, + "sunColor": [ + 1.0, + 0.949999988079071, + 0.800000011920929 + ], + "sunSphereSize": 5.0, + "timeOfDay": 8.35942554473877, + "timeSpeed": 0.12999999523162842 + }, + "transform": { + "position": { + "x": 4000.0, + "y": 10.0, + "z": 4000.0 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "children": [], + "id": 498, + "name": { + "name": "water" + }, + "transform": { + "position": { + "x": 4000.0, + "y": 0.0, + "z": 4000.0 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + }, + "waterPhysics": { + "defaultAngularDrag": 0.05000000074505806, + "defaultBuoyancy": 1.0, + "defaultLinearDrag": 0.25, + "defaultSubmergedThreshold": 0.10000000149011612, + "enabled": true, + "gravity": 9.8100004196167, + "waterDensity": 1000.0, + "waterSurfaceY": 6.0 + }, + "waterPlane": { + "autoUpdateFromWaterPhysics": true, + "enabled": true, + "planeSize": 12000.0, + "reflectivity": 0.3799999952316284, + "renderTextureSize": 512, + "tiling": 0.012000000104308128, + "waterColor": [ + 0.0, + 0.30000001192092896, + 0.5, + 0.800000011920929 + ], + "waterSurfaceY": 6.0, + "waveScale": 0.03099999949336052, + "waveSpeed": 0.9800000190734863 + } + } + ], + "version": "1.0" +} diff --git a/src/features/editScene/demos/demo-taxi/demo_scene_vehicletest.json b/src/features/editScene/demos/demo-taxi/demo_scene_vehicletest.json new file mode 100644 index 0000000..2328f0f --- /dev/null +++ b/src/features/editScene/demos/demo-taxi/demo_scene_vehicletest.json @@ -0,0 +1,688 @@ +{ + "actionDatabase": { + "actions": [ + { + "behaviorTree": { + "children": [ + { + "name": "vehicleEnter", + "params": "seat=driver", + "type": "luaTask" + } + ], + "type": "sequence" + }, + "cost": 1, + "effects": { + "bits": 0, + "mask": 0 + }, + "name": "vehicle_seat_driver", + "preconditions": { + "bits": 0, + "mask": 0 + } + }, + { + "behaviorTree": { + "children": [ + { + "name": "vehicleEnter", + "params": "seat=passenger", + "type": "luaTask" + } + ], + "type": "sequence" + }, + "cost": 1, + "effects": { + "bits": 0, + "mask": 0 + }, + "name": "vehicle_seat_passenger", + "preconditions": { + "bits": 0, + "mask": 0 + } + }, + { + "behaviorTree": { + "children": [ + { + "name": "vehicleEnter", + "params": "seat=rl", + "type": "luaTask" + } + ], + "type": "sequence" + }, + "cost": 1, + "effects": { + "bits": 0, + "mask": 0 + }, + "name": "vehicle_seat_rl", + "preconditions": { + "bits": 0, + "mask": 0 + } + }, + { + "behaviorTree": { + "children": [ + { + "name": "vehicleEnter", + "params": "seat=rr", + "type": "luaTask" + } + ], + "type": "sequence" + }, + "cost": 1, + "effects": { + "bits": 0, + "mask": 0 + }, + "name": "vehicle_seat_rr", + "preconditions": { + "bits": 0, + "mask": 0 + } + } + ], + "bitNames": [ + { + "index": 1, + "name": "hungry" + }, + { + "index": 2, + "name": "thirsty" + } + ], + "goals": [] + }, + "bookmarks": [], + "entities": [ + { + "children": [], + "id": 4294967794, + "light": { + "castShadows": false, + "constantAttenuation": 1.0, + "diffuseColor": { + "a": 1.0, + "b": 1.0, + "g": 1.0, + "r": 1.0 + }, + "direction": { + "x": 0.30000001192092896, + "y": -1.0, + "z": 0.20000000298023224 + }, + "intensity": 1.0, + "lightType": "directional", + "linearAttenuation": 0.0, + "quadraticAttenuation": 0.0, + "range": 100.0, + "specularColor": { + "a": 1.0, + "b": 0.5, + "g": 0.5, + "r": 0.5 + }, + "spotlightFalloff": 1.0, + "spotlightInnerAngle": 30.0, + "spotlightOuterAngle": 45.0 + }, + "name": { + "name": "demo_light" + }, + "transform": { + "position": { + "x": 0.0, + "y": 10.0, + "z": 0.0 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "children": [], + "collider": { + "halfHeight": 1.0, + "meshName": "", + "offset": { + "x": 0.0, + "y": -0.10000000149011612, + "z": 0.0 + }, + "parameters": { + "x": 100.0, + "y": 0.10000000149011612, + "z": 100.0 + }, + "radius": 0.5, + "rotationOffset": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "shapeType": "box" + }, + "id": 4294967792, + "name": { + "name": "demo_floor" + }, + "renderable": { + "meshName": "DemoFloorPlaneVehicleTest", + "visible": true + }, + "rigidBody": { + "bodyType": "static", + "enabled": true, + "friction": 0.800000011920929, + "isSensor": false, + "mass": 1.0, + "restitution": 0.0 + }, + "transform": { + "position": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "characterSpawner": { + "despawnDistance": 200.0, + "registryId": 2, + "spawnDistance": 100.0 + }, + "children": [], + "id": 4294967790, + "name": { + "name": "s1" + }, + "transform": { + "position": { + "x": 0.0, + "y": 0.10288965702056885, + "z": -6.0 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "children": [], + "id": 4294967789, + "name": { + "name": "player" + }, + "playerController": { + "actuatorColor": [ + 0.0, + 0.4000000059604645, + 1.0 + ], + "actuatorCooldown": 1.5, + "actuatorDistance": 25.0, + "actuatorLabelFontSize": 12.0, + "cameraMode": 0, + "distantCircleRadius": 8.0, + "fpsBoneName": "Head", + "idleState": "idle", + "locomotionStateMachine": "locomotion", + "mouseSensitivity": 0.20000000298023224, + "nearCircleRadius": 14.0, + "runState": "running", + "swimFastState": "swimming-fast", + "swimIdleState": "swim-idle", + "swimState": "swimming", + "targetCharacterName": "s1", + "tpsDistance": 3.0, + "tpsHeight": 2.0, + "walkState": "walking" + }, + "transform": { + "position": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "children": [ + { + "children": [], + "name": { + "name": "sedan1_door_rl" + }, + "renderable": { + "meshName": "sedan-v2-door-rl.mesh", + "visible": true + }, + "transform": { + "position": { + "x": 0.9192, + "y": -0.2869, + "z": -0.132 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + }, + "actuator": { + "actionNames": [ + "vehicle_seat_rl" + ], + "height": 2.0, + "radius": 1.2 + } + }, + { + "children": [], + "name": { + "name": "sedan1_door_rr" + }, + "renderable": { + "meshName": "sedan-v2-door-rr.mesh", + "visible": true + }, + "transform": { + "position": { + "x": -0.9192, + "y": -0.2869, + "z": -0.132 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + }, + "actuator": { + "actionNames": [ + "vehicle_seat_rr" + ], + "height": 2.0, + "radius": 1.2 + } + }, + { + "children": [], + "name": { + "name": "sedan1_door_fl" + }, + "renderable": { + "meshName": "sedan-v2-door-fl.mesh", + "visible": true + }, + "transform": { + "position": { + "x": 0.9192, + "y": -0.2868, + "z": 0.801 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + }, + "actuator": { + "actionNames": [ + "vehicle_seat_driver" + ], + "height": 2.0, + "radius": 1.2 + } + }, + { + "children": [], + "name": { + "name": "sedan1_door_fr" + }, + "renderable": { + "meshName": "sedan-v2-door-fr.mesh", + "visible": true + }, + "transform": { + "position": { + "x": -0.9192, + "y": -0.2868, + "z": 0.801 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + }, + "actuator": { + "actionNames": [ + "vehicle_seat_passenger" + ], + "height": 2.0, + "radius": 1.2 + } + }, + { + "children": [], + "name": { + "name": "sedan1_hood" + }, + "renderable": { + "meshName": "sedan-v2-hood.mesh", + "visible": true + }, + "transform": { + "position": { + "x": -0.0, + "y": 0.266, + "z": 0.9992 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + }, + { + "children": [], + "name": { + "name": "sedan1_trunk" + }, + "renderable": { + "meshName": "sedan-v2-trunk.mesh", + "visible": true + }, + "transform": { + "position": { + "x": 0.0, + "y": 0.2744, + "z": -1.446 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + } + } + ], + "collider": { + "halfHeight": 1.0, + "meshName": "", + "offset": { + "x": 0.0, + "y": 0.1535, + "z": -0.0629 + }, + "parameters": { + "x": 1.0107, + "y": 0.6126, + "z": 2.4834 + }, + "radius": 0.5, + "rotationOffset": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "shapeType": "box" + }, + "name": { + "name": "sedan1" + }, + "renderable": { + "meshName": "sedan-v2.mesh", + "visible": true + }, + "rigidBody": { + "bodyType": "dynamic", + "enabled": true, + "friction": 0.8, + "isSensor": false, + "mass": 1200.0, + "restitution": 0.0 + }, + "transform": { + "position": { + "x": 0.0, + "y": 1.5, + "z": 0.0 + }, + "rotation": { + "w": 1.0, + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0, + "z": 1.0 + } + }, + "vehicle": { + "cameraDistance": 6.0, + "cameraHeight": 1.6, + "engineBrake": 0.5, + "maxPitchRollAngleDeg": 60.0, + "maxTorque": 300.0, + "rearSteer": false, + "seatOffset": { + "x": 0.48, + "y": -0.28, + "z": 0.2012 + }, + "steeringRatio": 4.0, + "steeringWheelMeshName": "sedan-v2-steering-wheel.mesh", + "steeringWheelOffset": { + "x": 0.4915, + "y": 0.0807, + "z": 0.601 + }, + "steeringWheelTiltDeg": 25.0, + "wheelMeshName": "sedan-v2-wheel.mesh", + "wheels": [ + { + "driven": false, + "maxHandBrakeTorque": 0.0, + "maxSteerAngleDeg": 35.0, + "position": { + "x": 0.7805, + "y": -0.2052, + "z": 1.4806 + }, + "radius": 0.3918, + "suspensionDamping": 0.6, + "suspensionFrequency": 1.3, + "suspensionMaxLength": 0.35, + "suspensionMinLength": 0.1, + "width": 0.3145 + }, + { + "driven": false, + "maxHandBrakeTorque": 0.0, + "maxSteerAngleDeg": 35.0, + "position": { + "x": -0.7805, + "y": -0.2052, + "z": 1.4806 + }, + "radius": 0.3918, + "suspensionDamping": 0.6, + "suspensionFrequency": 1.3, + "suspensionMaxLength": 0.35, + "suspensionMinLength": 0.1, + "width": 0.3145 + }, + { + "driven": true, + "maxHandBrakeTorque": 300.0, + "maxSteerAngleDeg": 0.0, + "position": { + "x": 0.7805, + "y": -0.2052, + "z": -1.2806 + }, + "radius": 0.3918, + "suspensionDamping": 0.6, + "suspensionFrequency": 1.3, + "suspensionMaxLength": 0.35, + "suspensionMinLength": 0.1, + "width": 0.3145 + }, + { + "driven": true, + "maxHandBrakeTorque": 300.0, + "maxSteerAngleDeg": 0.0, + "position": { + "x": -0.7805, + "y": -0.2052, + "z": -1.2806 + }, + "radius": 0.3918, + "suspensionDamping": 0.6, + "suspensionFrequency": 1.3, + "suspensionMaxLength": 0.35, + "suspensionMinLength": 0.1, + "width": 0.3145 + } + ], + "seats": [ + { + "name": "driver", + "offset": { + "x": 0.48, + "y": -0.28, + "z": 0.2012 + }, + "driver": true + }, + { + "name": "passenger", + "offset": { + "x": -0.48, + "y": -0.28, + "z": 0.2012 + }, + "driver": false + }, + { + "name": "rl", + "offset": { + "x": 0.48, + "y": -0.28, + "z": -0.6032 + }, + "driver": false + }, + { + "name": "rr", + "offset": { + "x": -0.48, + "y": -0.28, + "z": -0.6032 + }, + "driver": false + } + ], + "seatCharacterScale": 1.0, + "tireFrictionLongitudinal": 1.3, + "tireFrictionLateral": 3.0, + "antiRollBarStiffness": 2000.0 + }, + "id": 500 + } + ], + "version": "1.0" +} diff --git a/src/features/editScene/demos/demo-taxi/merge_vehicle.py b/src/features/editScene/demos/demo-taxi/merge_vehicle.py new file mode 100644 index 0000000..336e041 --- /dev/null +++ b/src/features/editScene/demos/demo-taxi/merge_vehicle.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# +# Merge a generated vehicle entity (.vehicle.json, produced by +# assets/blender/scripts/gen_vehicle.py) into a demo scene JSON, +# replacing the entity with the given name. The scene entity keeps +# its id, name and transform (position/rotation/scale); everything +# else (collider, vehicle, renderable, children) is replaced by the +# generated fragment, with child entity names prefixed accordingly. +# +# This is how blend tweaks (seat empties, suspension, wheels, ...) +# propagate into the demo scenes after re-running import_vehicles: +# +# cmake --build build-vscode --target import_vehicles -j4 +# python3 merge_vehicle.py demo_scene_vehicletest.json \ +# ../../../../../../resources/vehicles/sedan-v2.vehicle.json sedan1 +# +# (paths relative to the build demo dir; the build scene JSONs are +# symlinks to the source dir, so merging there updates the source). +# +# Usage: +# merge_vehicle.py + +import json +import sys + + +def main(): + if len(sys.argv) != 4: + sys.exit("usage: merge_vehicle.py " + "") + scene_path, vehicle_path, entity_name = sys.argv[1:4] + + with open(scene_path) as f: + scene = json.load(f) + with open(vehicle_path) as f: + generated = json.load(f)["entities"][0] + gen_base = generated["name"]["name"] + + target = None + for e in scene["entities"]: + if e.get("name", {}).get("name") == entity_name: + target = e + break + if target is None: + sys.exit("merge_vehicle: entity '%s' not found in %s" % + (entity_name, scene_path)) + + # Rename the generated entity and its children to the target name. + def rename(node): + if "name" in node: + node["name"]["name"] = node["name"]["name"].replace( + gen_base, entity_name, 1) + for c in node.get("children", []): + rename(c) + + rename(generated) + + generated["id"] = target["id"] + generated["transform"] = target["transform"] + target.clear() + target.update(generated) + + with open(scene_path, "w") as f: + json.dump(scene, f, indent=2) + f.write("\n") + print("merge_vehicle: replaced '%s' in %s from %s" % + (entity_name, scene_path, vehicle_path)) + + +if __name__ == "__main__": + main() diff --git a/src/features/editScene/demos/demo-taxi/stage_runtime.cmake b/src/features/editScene/demos/demo-taxi/stage_runtime.cmake index 7052d6a..4a467f4 100644 --- a/src/features/editScene/demos/demo-taxi/stage_runtime.cmake +++ b/src/features/editScene/demos/demo-taxi/stage_runtime.cmake @@ -17,7 +17,8 @@ file(COPY "${EDITSCENE_SRC}/resources.cfg" DESTINATION "${DEMO_DIR}") file(COPY "${SRC_DIR}/project.json" DESTINATION "${DEMO_DIR}") -foreach(f demo_scene_interior.json demo_scene_exterior.json) +foreach(f demo_scene_interior.json demo_scene_exterior.json + demo_scene_vehicletest.json) set(link "${DEMO_DIR}/${f}") if(EXISTS "${link}" OR IS_SYMLINK "${link}") file(REMOVE "${link}") @@ -52,14 +53,18 @@ endforeach() # Runtime config JSONs loaded at startup relative to the CWD (game mode # reads startup_menu.json, the registries read the rest — the demo needs -# character_registry.json for the spawner's registryId 2 and -# animation_tree.json for the character's "male1_6" animation tree). They +# character_registry.json for the spawner's registryId 2). They # only exist after the editor/game has run once; copy them when present so # the demo behaves the same as when run from the editor binary directory. foreach(f startup_menu.json character_registry.json character_class.json - items.json item_state.json inventory_config.json - animation_tree.json) + items.json item_state.json inventory_config.json) if(EXISTS "${EDITSCENE_BIN}/${f}") file(COPY "${EDITSCENE_BIN}/${f}" DESTINATION "${DEMO_DIR}") endif() endforeach() + +# animation_tree.json is demo-owned (source tree, versioned with the demo; +# it carries the "driving" locomotion state used by the sedan seats). +# COPIED, not symlinked: AnimationTreeRegistry auto-saves on runtime tree +# migrations and must not write through into the source tree. +file(COPY "${SRC_DIR}/animation_tree.json" DESTINATION "${DEMO_DIR}")