Custom model sedan was integrated.

This commit is contained in:
2026-09-17 02:51:45 +03:00
parent e2e4a398a9
commit c748a7dd1e
8 changed files with 5017 additions and 784 deletions
+143 -9
View File
@@ -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
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -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"
}
@@ -0,0 +1,71 @@
#!/usr/bin/env python3
#
# Merge a generated vehicle entity (<name>.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 <scene.json> <vehicle.json> <entityName>
import json
import sys
def main():
if len(sys.argv) != 4:
sys.exit("usage: merge_vehicle.py <scene.json> <vehicle.json> "
"<entityName>")
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()
@@ -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}")