taxi demo: Taxi integrated, phase 1 complete
This commit is contained in:
@@ -275,6 +275,10 @@ cd demos/demo-taxi
|
||||
# for 150 frames; PASS needs >= 45 deg of yaw - catches the
|
||||
# "drives like it's on ice" regression):
|
||||
./demoTaxi --headless --test-vehicle-turn
|
||||
# ...or the coast check (W for 200 frames, release, 240 coast frames;
|
||||
# PASS needs > 5 m/s at the end - cars have engineBrake 0 and must not
|
||||
# brake by themselves when the accelerator is released):
|
||||
./demoTaxi --headless --test-vehicle-coast
|
||||
# NOTE: always pass --headless to the tests — without it the frame
|
||||
# delta is wall time (a few FPS under xvfb), so the fixed-length
|
||||
# W-hold drives the sedan clear off the test floor and the exit-spot
|
||||
@@ -285,18 +289,22 @@ cd demos/demo-taxi
|
||||
# handbrake while driving, Escape = pause menu (frees the cursor).
|
||||
#
|
||||
# Phase 1 sedans: sedan1/sedan_plain are the hand-modeled
|
||||
# sedan-v2.blend via the convention-based generator
|
||||
# sedan-v2.blend and sedan_taxi is sedan-v2-taxi.blend (same base car
|
||||
# with taxi livery + roof sign), all via the convention-based generator
|
||||
# assets/blender/scripts/gen_vehicle.py (VEHICLES_GEN_SRC in
|
||||
# assets/blender/vehicles/CMakeLists.txt) - seat_* empties mark the
|
||||
# character root pins, the driving state plays the sitting-vehicle
|
||||
# animation (no offsets, seatCharacterScale 1.0); see
|
||||
# assets/blender/vehicles/CAR_MODELING.md. sedan_taxi is still the
|
||||
# older procedural gen_taxi_assets.py car (sitting pose at
|
||||
# seatCharacterScale 0.85) pending a v2-based taxi model. The exterior
|
||||
# assets/blender/vehicles/CAR_MODELING.md. After editing a vehicle
|
||||
# blend run `cmake --build build-vscode --target update_vehicles` (one
|
||||
# step: re-export, re-stage resources, re-merge both demo scenes).
|
||||
# The exterior
|
||||
# scene parks sedan_taxi and sedan_plain near the spawn point; each
|
||||
# door child has an actuator with a vehicle_seat_* action (driver,
|
||||
# passenger, rl, rr) that seats the player at that door's seat via the
|
||||
# vehicle_enter event (vehicle resolved by proximity).
|
||||
# vehicle_enter event (vehicle resolved by proximity). Stepping out of
|
||||
# a stopped/almost-stopped vehicle parks it (handbrake stays engaged
|
||||
# until a driver seats again); bailing out at speed lets the car coast.
|
||||
# Visual tuning aids (work in any scene):
|
||||
./demoTaxi demo_scene_exterior.json --force-pos 4006 11 3972 90 -10 \
|
||||
--debug-enter-vehicle sedan_taxi --screenshot 300 /tmp/seat.png
|
||||
@@ -1064,7 +1072,10 @@ driving),
|
||||
`rearSteer` (flips the steer sign for rear-steered vehicles like the
|
||||
forklift, so D still turns the nose right), `engineBrake` (brake factor
|
||||
applied automatically when coasting above 0.5 m/s with no throttle —
|
||||
keeps the forklift from rolling forever), `wheelMeshName`, the steering
|
||||
0 for cars so they coast when the accelerator is released (braking is
|
||||
the driver's job: S while rolling forward brakes via the
|
||||
brake-before-reverse rule, Space is the handbrake), ~0.5 for slow yard
|
||||
vehicles like the forklift so they do not roll forever), `wheelMeshName`, the steering
|
||||
wheel visual (`steeringWheelMeshName` — mesh modelled at the origin
|
||||
with its spin axis on local Y —, `steeringWheelOffset` in chassis
|
||||
space, `steeringWheelTiltDeg`, `steeringRatio` steering-wheel degrees
|
||||
@@ -1255,19 +1266,28 @@ makes vehicles drivable by the player:
|
||||
- **Driving**: `update()` (called right before `PlayerControllerSystem` in
|
||||
Game+Playing) feeds `GameInputState` into the vehicle inputs — W/S
|
||||
`inputForward`, A/D `inputRight`, Space `inputHandBrake` — only when
|
||||
the player sits on a driver seat; passengers zero the inputs.
|
||||
the player sits on a driver seat; passengers zero the inputs except
|
||||
`inputHandBrake` (it is part of the parked state, see below).
|
||||
- **Exit**: holding E while driving (a cooldown of `EXIT_MIN_SEAT_TIME`
|
||||
0.75 s after seating must pass first, so the entering tap never counts,
|
||||
then E must be held for `EXIT_HOLD_TIME` 0.5 s) picks the first
|
||||
raycast-clear spot beside the door of the seat actually occupied
|
||||
(sideways from the seat's own chassis-space row, own side first, then
|
||||
(sideways from the seat's own chassis-space row with the direction
|
||||
vectors flattened to horizontal — a tilted car on rough terrain must
|
||||
not swing the spots off the ground —, own side first, then
|
||||
the opposite side of the same row, then behind/ahead (±Z) of the
|
||||
chassis as fallbacks; horizontal clearance ray + downward ground ray;
|
||||
`raycastQuery` sees NON_MOVING only, so the vehicle never blocks its
|
||||
own exit), moves the
|
||||
chassis as fallbacks; the ground ray runs first and the horizontal
|
||||
clearance ray goes from above the vehicle to just above the ground at
|
||||
the spot, so a gentle slope does not swallow it; `raycastQuery` sees
|
||||
NON_MOVING only, so the vehicle never blocks its own exit), moves the
|
||||
character node there, re-enables physics (the CharacterSystem diff check
|
||||
teleports the capsule to the node) and restores the on-foot camera
|
||||
settings. After an exit the E key must be released once before a
|
||||
settings. Exiting a stopped or nearly stopped vehicle (|speed| <=
|
||||
`EXIT_PARK_MAX_SPEED` 1.0 m/s) parks it: `inputHandBrake` is left
|
||||
engaged (VehicleSystem keeps feeding it every physics frame) until a
|
||||
driver seats again and `update()` overwrites the inputs; bailing out
|
||||
at speed lets the car coast. After an exit the E key must be released
|
||||
once before a
|
||||
`vehicle_enter` event is accepted again (`m_eReleaseRequired`), so the
|
||||
still-held exit key cannot immediately re-seat the player; the
|
||||
E/F/I/Space pressed edges also ignore SDL key repeats
|
||||
|
||||
@@ -343,7 +343,30 @@ 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.
|
||||
needed. Finally, cars no longer brake by themselves when the
|
||||
accelerator is released: gen_vehicle.py now emits `engineBrake` 0
|
||||
(the forklift keeps 0.5 so it does not roll forever), so a sedan
|
||||
coasts until the driver brakes (S while rolling forward brakes via the
|
||||
existing brake-before-reverse rule, Space is the handbrake) — a parked
|
||||
car on a slope legitimately rolls. `--test-vehicle-coast` drives
|
||||
sedan1 for 200 frames, releases W and requires the car to still roll
|
||||
above 5 m/s after 240 coast frames; it then jumps off at speed and
|
||||
requires the car to keep coasting, while the drive test's driver-exit
|
||||
leg stops the car first and requires the exit to park it (handbrake
|
||||
stays engaged, car stays put even on the exterior slope —
|
||||
`VehicleControllerSystem` parks on exit below 1 m/s and passengers no
|
||||
longer release the handbrake). The exit-spot probe directions are
|
||||
flattened to horizontal (a car resting tilted on rough terrain used to
|
||||
swing the side spots meters up/down so the ground ray missed real
|
||||
ground) and the clearance ray targets the ground height at the spot.
|
||||
|
||||
**sedan-v2-taxi (the taxi).** `sedan_taxi` in the exterior scene is
|
||||
now `assets/blender/vehicles/sedan-v2-taxi.blend` — same base car as
|
||||
sedan-v2 (identical part/seat naming, shared wheel mesh) with taxi
|
||||
livery and roof sign — exported by the same gen_vehicle.py pass and
|
||||
merged by `update_vehicles`; `sedan-v2-taxi-glass` joined
|
||||
`kGlassMaterialNames` for the near/far glass swap. The old procedural
|
||||
gen_taxi_assets.py taxi is retired.
|
||||
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):
|
||||
@@ -355,6 +378,7 @@ Verify:
|
||||
```bash
|
||||
./demoTaxi --headless --test-vehicle-drive demo_scene_vehicletest.json
|
||||
./demoTaxi --headless --test-vehicle-drive # exterior streaming terrain
|
||||
./demoTaxi --headless --test-vehicle-coast # coasting + park/jump-off
|
||||
```
|
||||
|
||||
### Phase 2 — Road geometry & compliance improvements
|
||||
|
||||
@@ -281,7 +281,8 @@ static void createFloorMesh(const Ogre::String &meshName,
|
||||
* extend it when adding car families.
|
||||
*/
|
||||
static const char *kGlassMaterialNames[] = {"sedan-glass",
|
||||
"sedan-v2-glass"};
|
||||
"sedan-v2-glass",
|
||||
"sedan-v2-taxi-glass"};
|
||||
|
||||
static void fixupSedanGlassMaterial()
|
||||
{
|
||||
@@ -965,6 +966,7 @@ struct VehicleDriveTestListener : public Ogre::FrameListener {
|
||||
bool failed = false;
|
||||
Ogre::String failReason;
|
||||
Ogre::Vector3 startPos = Ogre::Vector3::ZERO;
|
||||
Ogre::Vector3 parkPos = Ogre::Vector3::ZERO;
|
||||
Ogre::Vector3 authoredPos = Ogre::Vector3::ZERO;
|
||||
bool authoredValid = false;
|
||||
float lastY = 0.0f;
|
||||
@@ -1241,14 +1243,26 @@ struct VehicleDriveTestListener : public Ogre::FrameListener {
|
||||
break;
|
||||
}
|
||||
case 6: {
|
||||
/* Let the steering settle back, then exit. */
|
||||
/* Let the steering settle back, then stop the car
|
||||
* (as a driver braking to a stop would) and exit —
|
||||
* a stopped car must get parked by the exit. */
|
||||
if (frame - driveStart < 60)
|
||||
break;
|
||||
{
|
||||
const RigidBodyComponent &rb =
|
||||
sedan.get<RigidBodyComponent>();
|
||||
app->getEditorPhysicsSystem()
|
||||
->getPhysicsWrapper()
|
||||
->setVelocities(rb.bodyID,
|
||||
Ogre::Vector3::ZERO,
|
||||
Ogre::Vector3::ZERO);
|
||||
}
|
||||
const TransformComponent &ft =
|
||||
sedan.get<TransformComponent>();
|
||||
Ogre::Vector3 pos = ft.node ?
|
||||
ft.node->_getDerivedPosition() :
|
||||
ft.position;
|
||||
parkPos = pos;
|
||||
vcs->exitVehicle();
|
||||
if (vcs->isDriving()) {
|
||||
failed = true;
|
||||
@@ -1307,6 +1321,33 @@ struct VehicleDriveTestListener : public Ogre::FrameListener {
|
||||
* NOT become player-controlled. */
|
||||
if (frame - driveStart < 30)
|
||||
break;
|
||||
/* The driver exited a stopped car: it must be
|
||||
* parked (handbrake) and stay put even on the
|
||||
* exterior scene's slope. */
|
||||
{
|
||||
const TransformComponent &t =
|
||||
sedan.get<TransformComponent>();
|
||||
Ogre::Vector3 pos =
|
||||
t.node ? t.node->_getDerivedPosition() :
|
||||
t.position;
|
||||
float drift = (pos - parkPos).length();
|
||||
const VehicleComponent &vc =
|
||||
sedan.get<VehicleComponent>();
|
||||
if (vc.inputHandBrake < 1.0f || drift > 0.5f) {
|
||||
failed = true;
|
||||
failReason =
|
||||
vehicleName +
|
||||
" not parked after the driver "
|
||||
"exited a stopped car (handbrake " +
|
||||
Ogre::StringConverter::toString(
|
||||
vc.inputHandBrake) +
|
||||
", drift " +
|
||||
Ogre::StringConverter::toString(
|
||||
drift) +
|
||||
")";
|
||||
break;
|
||||
}
|
||||
}
|
||||
editScene::EventParams params;
|
||||
params.setString("vehicle", vehicleName);
|
||||
params.setString("seat", "passenger");
|
||||
@@ -1378,13 +1419,24 @@ struct VehicleDriveTestListener : public Ogre::FrameListener {
|
||||
t.node->_getDerivedPosition() :
|
||||
t.position;
|
||||
float moved = (pos - startPos).length();
|
||||
if (moved > 1.0f) {
|
||||
/* The contract is that no driving inputs reach the
|
||||
* vehicle from a passenger seat; a positional check is
|
||||
* meaningless now that cars coast (a parked car on a
|
||||
* slope legitimately rolls). */
|
||||
const VehicleComponent &vic =
|
||||
sedan.get<VehicleComponent>();
|
||||
if (vic.inputForward != 0.0f ||
|
||||
vic.inputRight != 0.0f) {
|
||||
failed = true;
|
||||
failReason =
|
||||
vehicleName +
|
||||
" moved with passenger-seat W input "
|
||||
"(moved " +
|
||||
Ogre::StringConverter::toString(moved) +
|
||||
" got driving inputs from the "
|
||||
"passenger seat (forward " +
|
||||
Ogre::StringConverter::toString(
|
||||
vic.inputForward) +
|
||||
", right " +
|
||||
Ogre::StringConverter::toString(
|
||||
vic.inputRight) +
|
||||
")";
|
||||
break;
|
||||
}
|
||||
@@ -1421,9 +1473,22 @@ struct VehicleDriveTestListener : public Ogre::FrameListener {
|
||||
Ogre::Vector3 right =
|
||||
vt.node->_getDerivedOrientation() *
|
||||
Ogre::Vector3::UNIT_X;
|
||||
float lat = (cpos - vt.node->_getDerivedPosition())
|
||||
.dotProduct(right);
|
||||
if (lat * seatX <= 0.0f) {
|
||||
/* Flatten both vectors: the exit
|
||||
* spots are computed horizontally,
|
||||
* and a car resting tilted on rough
|
||||
* terrain must not skew the metric
|
||||
* (the character drops to the
|
||||
* ground below a tilted chassis). */
|
||||
right.y = 0.0f;
|
||||
if (right.squaredLength() < 0.01f)
|
||||
right = Ogre::Vector3::UNIT_X;
|
||||
right.normalise();
|
||||
Ogre::Vector3 delta =
|
||||
cpos - vt.node->_getDerivedPosition();
|
||||
delta.y = 0.0f;
|
||||
float lat = delta.dotProduct(right);
|
||||
if (lat * seatX <= 0.0f ||
|
||||
delta.length() < 1.0f) {
|
||||
failed = true;
|
||||
failReason =
|
||||
"exited on the wrong side "
|
||||
@@ -1768,6 +1833,162 @@ struct VehicleRightingTestListener : public Ogre::FrameListener {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* --test-vehicle-coast: cars must NOT brake by themselves when the
|
||||
* accelerator is released (engineBrake 0; braking is the driver's job
|
||||
* via S-while-rolling / Space). Enters sedan1 (vehicletest scene),
|
||||
* holds W for 200 frames, releases all inputs and coasts for 240
|
||||
* frames, requiring the car to still be rolling above 5 m/s
|
||||
* (engineBrake 0.5 would have stopped it); then jumps off at speed and
|
||||
* requires the car to keep coasting (parking the car only happens when
|
||||
* the driver steps out of a nearly stopped vehicle).
|
||||
*/
|
||||
struct VehicleCoastTestListener : public Ogre::FrameListener {
|
||||
EditorApp *app;
|
||||
Ogre::String vehicleName = "sedan1";
|
||||
int frame = 0;
|
||||
/* 0: wait ready, 1: wait settle, 2: W drive, 3: coast,
|
||||
* 4: exit at speed + keep-coasting check, 5: done */
|
||||
int phase = 0;
|
||||
bool failed = false;
|
||||
Ogre::String failReason;
|
||||
int driveStart = 0;
|
||||
float speedAtRelease = 0.0f;
|
||||
|
||||
VehicleCoastTestListener(EditorApp *a)
|
||||
: app(a)
|
||||
{
|
||||
}
|
||||
|
||||
bool frameRenderingQueued(const Ogre::FrameEvent &) override
|
||||
{
|
||||
frame++;
|
||||
if (failed || phase == 5) {
|
||||
app->getRoot()->queueEndRendering();
|
||||
return true;
|
||||
}
|
||||
if (frame > 20000) {
|
||||
failed = true;
|
||||
failReason = "timeout in vehicle coast test";
|
||||
app->getRoot()->queueEndRendering();
|
||||
return true;
|
||||
}
|
||||
|
||||
flecs::entity sedan = flecs::entity::null();
|
||||
app->getWorld()->query<EntityNameComponent>().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<RigidBodyComponent>() ||
|
||||
!sedan.has<VehicleComponent>())
|
||||
return true;
|
||||
const RigidBodyComponent &rb = sedan.get<RigidBodyComponent>();
|
||||
const VehicleComponent &v = sedan.get<VehicleComponent>();
|
||||
if (!rb.bodyCreated || !v.constraintCreated)
|
||||
return true;
|
||||
if (!app->getPlayerCharacterEntity().is_alive())
|
||||
return true;
|
||||
|
||||
JoltPhysicsWrapper *phys =
|
||||
app->getEditorPhysicsSystem()->getPhysicsWrapper();
|
||||
|
||||
switch (phase) {
|
||||
case 0:
|
||||
phase = 1;
|
||||
break;
|
||||
case 1: {
|
||||
if (phys->getLinearVelocity(rb.bodyID).length() >
|
||||
0.05f)
|
||||
break;
|
||||
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 < 200)
|
||||
break;
|
||||
app->getGameInputState().w = false;
|
||||
speedAtRelease =
|
||||
phys->getLinearVelocity(rb.bodyID).length();
|
||||
driveStart = frame;
|
||||
phase = 3;
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
if (frame - driveStart < 240)
|
||||
break;
|
||||
float speed =
|
||||
phys->getLinearVelocity(rb.bodyID).length();
|
||||
std::cout << "[test] coast-test: speed at release "
|
||||
<< speedAtRelease << " m/s, after 240 "
|
||||
"coast frames "
|
||||
<< speed << " m/s" << std::endl;
|
||||
if (speedAtRelease > 5.0f && speed < 5.0f) {
|
||||
failed = true;
|
||||
failReason = "car braked by itself while "
|
||||
"coasting (engineBrake regression)";
|
||||
break;
|
||||
}
|
||||
/* Jump off at speed: the car must keep coasting
|
||||
* (no parking brake). */
|
||||
VehicleControllerSystem *vcs =
|
||||
app->getVehicleControllerSystem();
|
||||
vcs->exitVehicle();
|
||||
if (vcs->isDriving()) {
|
||||
failed = true;
|
||||
failReason = "exitVehicle at speed did not "
|
||||
"end driving";
|
||||
break;
|
||||
}
|
||||
driveStart = frame;
|
||||
phase = 4;
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
if (frame - driveStart < 60)
|
||||
break;
|
||||
float speed =
|
||||
phys->getLinearVelocity(rb.bodyID).length();
|
||||
const VehicleComponent &vc =
|
||||
sedan.get<VehicleComponent>();
|
||||
std::cout << "[test] coast-test: after jumping off "
|
||||
"at speed, 60 frames later "
|
||||
<< speed << " m/s (handbrake "
|
||||
<< vc.inputHandBrake << ")" << std::endl;
|
||||
if (vc.inputHandBrake != 0.0f || speed < 3.0f) {
|
||||
failed = true;
|
||||
failReason = "car parked after the driver "
|
||||
"jumped off at speed";
|
||||
break;
|
||||
}
|
||||
std::cout << "[test] PASS" << std::endl;
|
||||
phase = 5;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* demo-taxi: taxi driver demo. Starts as an exact copy of
|
||||
* demos/demo-interior-exterior-dynamics (same scene-switch-door setup,
|
||||
@@ -1937,6 +2158,7 @@ int main(int argc, char *argv[])
|
||||
bool testVehicleDrive = false;
|
||||
bool testVehicleTurn = false;
|
||||
bool testVehicleRighting = false;
|
||||
bool testVehicleCoast = false;
|
||||
bool sceneArgGiven = false;
|
||||
Ogre::String shotFile;
|
||||
int shotFrame = -1;
|
||||
@@ -1965,6 +2187,8 @@ int main(int argc, char *argv[])
|
||||
testVehicleTurn = true;
|
||||
} else if (arg == "--test-vehicle-righting") {
|
||||
testVehicleRighting = true;
|
||||
} else if (arg == "--test-vehicle-coast") {
|
||||
testVehicleCoast = true;
|
||||
} else if (arg == "--debug-buoyancy") {
|
||||
app.setDebugBuoyancy(true);
|
||||
} else if (arg == "--debug-enter-vehicle" &&
|
||||
@@ -2017,6 +2241,9 @@ int main(int argc, char *argv[])
|
||||
* floor. */
|
||||
if (testVehicleRighting && !sceneArgGiven)
|
||||
sceneFile = "demo_scene_vehicletest.json";
|
||||
/* The coast test too. */
|
||||
if (testVehicleCoast && !sceneArgGiven)
|
||||
sceneFile = "demo_scene_vehicletest.json";
|
||||
app.setHeadless(headless);
|
||||
if (headless) {
|
||||
/* Headless frames take ~1ms of wall time; pin a 60Hz
|
||||
@@ -2082,6 +2309,10 @@ int main(int argc, char *argv[])
|
||||
app.getRoot()->addFrameListener(
|
||||
&vehicleRightingListener);
|
||||
|
||||
VehicleCoastTestListener vehicleCoastListener(&app);
|
||||
if (testVehicleCoast)
|
||||
app.getRoot()->addFrameListener(&vehicleCoastListener);
|
||||
|
||||
/* Window glass near/far swap (always on). */
|
||||
SedanGlassLodListener glassListener(&app);
|
||||
app.getRoot()->addFrameListener(&glassListener);
|
||||
|
||||
@@ -606,27 +606,19 @@
|
||||
{
|
||||
"children": [
|
||||
{
|
||||
"actuator": {
|
||||
"actionNames": [
|
||||
"vehicle_seat_driver"
|
||||
],
|
||||
"height": 2.0,
|
||||
"radius": 1.2
|
||||
},
|
||||
"children": [],
|
||||
"id": 610,
|
||||
"name": {
|
||||
"name": "sedan_taxi_door_fl"
|
||||
"name": "sedan_taxi_door_rl"
|
||||
},
|
||||
"renderable": {
|
||||
"meshName": "sedan-taxi-door-fl.mesh",
|
||||
"meshName": "sedan-v2-taxi-door-rl.mesh",
|
||||
"visible": true
|
||||
},
|
||||
"transform": {
|
||||
"position": {
|
||||
"x": 0.855,
|
||||
"y": -0.1,
|
||||
"z": 0.55
|
||||
"x": 0.9192,
|
||||
"y": -0.2869,
|
||||
"z": -0.132
|
||||
},
|
||||
"rotation": {
|
||||
"w": 1.0,
|
||||
@@ -639,66 +631,29 @@
|
||||
"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"
|
||||
"name": "sedan_taxi_door_rr"
|
||||
},
|
||||
"renderable": {
|
||||
"meshName": "sedan-taxi-door-rl.mesh",
|
||||
"meshName": "sedan-v2-taxi-door-rr.mesh",
|
||||
"visible": true
|
||||
},
|
||||
"transform": {
|
||||
"position": {
|
||||
"x": 0.855,
|
||||
"y": -0.1,
|
||||
"z": -0.46
|
||||
"x": -0.9192,
|
||||
"y": -0.2869,
|
||||
"z": -0.132
|
||||
},
|
||||
"rotation": {
|
||||
"w": 1.0,
|
||||
@@ -711,30 +666,29 @@
|
||||
"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"
|
||||
"name": "sedan_taxi_door_fl"
|
||||
},
|
||||
"renderable": {
|
||||
"meshName": "sedan-taxi-door-rr.mesh",
|
||||
"meshName": "sedan-v2-taxi-door-fl.mesh",
|
||||
"visible": true
|
||||
},
|
||||
"transform": {
|
||||
"position": {
|
||||
"x": -0.855,
|
||||
"y": -0.1,
|
||||
"z": -0.46
|
||||
"x": 0.9192,
|
||||
"y": -0.2868,
|
||||
"z": 0.801
|
||||
},
|
||||
"rotation": {
|
||||
"w": 1.0,
|
||||
@@ -747,23 +701,64 @@
|
||||
"y": 1.0,
|
||||
"z": 1.0
|
||||
}
|
||||
},
|
||||
"actuator": {
|
||||
"actionNames": [
|
||||
"vehicle_seat_driver"
|
||||
],
|
||||
"height": 2.0,
|
||||
"radius": 1.2
|
||||
}
|
||||
},
|
||||
{
|
||||
"children": [],
|
||||
"name": {
|
||||
"name": "sedan_taxi_door_fr"
|
||||
},
|
||||
"renderable": {
|
||||
"meshName": "sedan-v2-taxi-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": [],
|
||||
"id": 614,
|
||||
"name": {
|
||||
"name": "sedan_taxi_hood"
|
||||
},
|
||||
"renderable": {
|
||||
"meshName": "sedan-taxi-hood.mesh",
|
||||
"meshName": "sedan-v2-taxi-hood.mesh",
|
||||
"visible": true
|
||||
},
|
||||
"transform": {
|
||||
"position": {
|
||||
"x": 0.0,
|
||||
"y": 0.17,
|
||||
"z": 0.75
|
||||
"x": -0.0,
|
||||
"y": 0.266,
|
||||
"z": 0.9992
|
||||
},
|
||||
"rotation": {
|
||||
"w": 1.0,
|
||||
@@ -780,19 +775,18 @@
|
||||
},
|
||||
{
|
||||
"children": [],
|
||||
"id": 615,
|
||||
"name": {
|
||||
"name": "sedan_taxi_trunk"
|
||||
},
|
||||
"renderable": {
|
||||
"meshName": "sedan-taxi-trunk.mesh",
|
||||
"meshName": "sedan-v2-taxi-trunk.mesh",
|
||||
"visible": true
|
||||
},
|
||||
"transform": {
|
||||
"position": {
|
||||
"x": 0.0,
|
||||
"y": 0.27,
|
||||
"z": -1.35
|
||||
"y": 0.2744,
|
||||
"z": -1.446
|
||||
},
|
||||
"rotation": {
|
||||
"w": 1.0,
|
||||
@@ -813,13 +807,13 @@
|
||||
"meshName": "",
|
||||
"offset": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
"y": 0.3821,
|
||||
"z": -0.0629
|
||||
},
|
||||
"parameters": {
|
||||
"x": 0.9,
|
||||
"y": 0.35,
|
||||
"z": 2.4
|
||||
"x": 1.0107,
|
||||
"y": 0.8412,
|
||||
"z": 2.4834
|
||||
},
|
||||
"radius": 0.5,
|
||||
"rotationOffset": {
|
||||
@@ -830,12 +824,11 @@
|
||||
},
|
||||
"shapeType": "box"
|
||||
},
|
||||
"id": 600,
|
||||
"name": {
|
||||
"name": "sedan_taxi"
|
||||
},
|
||||
"renderable": {
|
||||
"meshName": "sedan-taxi.mesh",
|
||||
"meshName": "sedan-v2-taxi.mesh",
|
||||
"visible": true
|
||||
},
|
||||
"rigidBody": {
|
||||
@@ -867,130 +860,134 @@
|
||||
"vehicle": {
|
||||
"cameraDistance": 6.0,
|
||||
"cameraHeight": 1.6,
|
||||
"engineBrake": 0.5,
|
||||
"engineBrake": 0.0,
|
||||
"maxPitchRollAngleDeg": 60.0,
|
||||
"maxTorque": 300.0,
|
||||
"rearSteer": false,
|
||||
"seatOffset": {
|
||||
"x": -0.35,
|
||||
"y": 0.15,
|
||||
"z": -0.1
|
||||
"x": 0.48,
|
||||
"y": -0.28,
|
||||
"z": 0.2012
|
||||
},
|
||||
"steeringRatio": 4.0,
|
||||
"steeringWheelMeshName": "sedan-steering-wheel.mesh",
|
||||
"steeringWheelMeshName": "sedan-v2-taxi-steering-wheel.mesh",
|
||||
"steeringWheelOffset": {
|
||||
"x": 0.35,
|
||||
"y": 0.45,
|
||||
"z": 0.2
|
||||
"x": 0.4915,
|
||||
"y": 0.0807,
|
||||
"z": 0.601
|
||||
},
|
||||
"steeringWheelTiltDeg": 25.0,
|
||||
"wheelMeshName": "sedan-wheel.mesh",
|
||||
"wheelMeshName": "sedan-v2-taxi-wheel.mesh",
|
||||
"wheels": [
|
||||
{
|
||||
"driven": false,
|
||||
"maxHandBrakeTorque": 0.0,
|
||||
"maxSteerAngleDeg": 35.0,
|
||||
"position": {
|
||||
"x": -0.8,
|
||||
"y": -0.25,
|
||||
"z": 1.5
|
||||
"x": 0.7805,
|
||||
"y": -0.2052,
|
||||
"z": 1.4806
|
||||
},
|
||||
"radius": 0.33,
|
||||
"suspensionDamping": 0.7,
|
||||
"suspensionFrequency": 1.8,
|
||||
"radius": 0.3918,
|
||||
"suspensionDamping": 0.6,
|
||||
"suspensionFrequency": 1.3,
|
||||
"suspensionMaxLength": 0.35,
|
||||
"suspensionMinLength": 0.1,
|
||||
"width": 0.22
|
||||
"width": 0.3145
|
||||
},
|
||||
{
|
||||
"driven": false,
|
||||
"maxHandBrakeTorque": 0.0,
|
||||
"maxSteerAngleDeg": 35.0,
|
||||
"position": {
|
||||
"x": 0.8,
|
||||
"y": -0.25,
|
||||
"z": 1.5
|
||||
"x": -0.7805,
|
||||
"y": -0.2052,
|
||||
"z": 1.4806
|
||||
},
|
||||
"radius": 0.33,
|
||||
"suspensionDamping": 0.7,
|
||||
"suspensionFrequency": 1.8,
|
||||
"radius": 0.3918,
|
||||
"suspensionDamping": 0.6,
|
||||
"suspensionFrequency": 1.3,
|
||||
"suspensionMaxLength": 0.35,
|
||||
"suspensionMinLength": 0.1,
|
||||
"width": 0.22
|
||||
"width": 0.3145
|
||||
},
|
||||
{
|
||||
"driven": true,
|
||||
"maxHandBrakeTorque": 300.0,
|
||||
"maxSteerAngleDeg": 0.0,
|
||||
"position": {
|
||||
"x": -0.8,
|
||||
"y": -0.25,
|
||||
"z": -1.5
|
||||
"x": 0.7805,
|
||||
"y": -0.2052,
|
||||
"z": -1.2806
|
||||
},
|
||||
"radius": 0.33,
|
||||
"suspensionDamping": 0.7,
|
||||
"suspensionFrequency": 1.8,
|
||||
"radius": 0.3918,
|
||||
"suspensionDamping": 0.6,
|
||||
"suspensionFrequency": 1.3,
|
||||
"suspensionMaxLength": 0.35,
|
||||
"suspensionMinLength": 0.1,
|
||||
"width": 0.22
|
||||
"width": 0.3145
|
||||
},
|
||||
{
|
||||
"driven": true,
|
||||
"maxHandBrakeTorque": 300.0,
|
||||
"maxSteerAngleDeg": 0.0,
|
||||
"position": {
|
||||
"x": 0.8,
|
||||
"y": -0.25,
|
||||
"z": -1.5
|
||||
"x": -0.7805,
|
||||
"y": -0.2052,
|
||||
"z": -1.2806
|
||||
},
|
||||
"radius": 0.33,
|
||||
"suspensionDamping": 0.7,
|
||||
"suspensionFrequency": 1.8,
|
||||
"radius": 0.3918,
|
||||
"suspensionDamping": 0.6,
|
||||
"suspensionFrequency": 1.3,
|
||||
"suspensionMaxLength": 0.35,
|
||||
"suspensionMinLength": 0.1,
|
||||
"width": 0.22
|
||||
"width": 0.3145
|
||||
}
|
||||
],
|
||||
"seats": [
|
||||
{
|
||||
"name": "driver",
|
||||
"offset": {
|
||||
"x": 0.35,
|
||||
"y": -0.22,
|
||||
"z": 0.25
|
||||
"x": 0.48,
|
||||
"y": -0.28,
|
||||
"z": 0.2012
|
||||
},
|
||||
"driver": true
|
||||
},
|
||||
{
|
||||
"name": "passenger",
|
||||
"offset": {
|
||||
"x": -0.35,
|
||||
"y": -0.22,
|
||||
"z": 0.25
|
||||
"x": -0.48,
|
||||
"y": -0.28,
|
||||
"z": 0.2012
|
||||
},
|
||||
"driver": false
|
||||
},
|
||||
{
|
||||
"name": "rl",
|
||||
"offset": {
|
||||
"x": 0.35,
|
||||
"y": -0.22,
|
||||
"z": -0.35
|
||||
"x": 0.48,
|
||||
"y": -0.28,
|
||||
"z": -0.6032
|
||||
},
|
||||
"driver": false
|
||||
},
|
||||
{
|
||||
"name": "rr",
|
||||
"offset": {
|
||||
"x": -0.35,
|
||||
"y": -0.22,
|
||||
"z": -0.35
|
||||
"x": -0.48,
|
||||
"y": -0.28,
|
||||
"z": -0.6032
|
||||
},
|
||||
"driver": false
|
||||
}
|
||||
],
|
||||
"seatCharacterScale": 0.85
|
||||
}
|
||||
"seatCharacterScale": 1.0,
|
||||
"tireFrictionLongitudinal": 1.3,
|
||||
"tireFrictionLateral": 3.0,
|
||||
"antiRollBarStiffness": 2000.0
|
||||
},
|
||||
"id": 600
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
@@ -1249,7 +1246,7 @@
|
||||
"vehicle": {
|
||||
"cameraDistance": 6.0,
|
||||
"cameraHeight": 1.6,
|
||||
"engineBrake": 0.5,
|
||||
"engineBrake": 0.0,
|
||||
"maxPitchRollAngleDeg": 60.0,
|
||||
"maxTorque": 300.0,
|
||||
"rearSteer": false,
|
||||
|
||||
@@ -554,7 +554,7 @@
|
||||
"vehicle": {
|
||||
"cameraDistance": 6.0,
|
||||
"cameraHeight": 1.6,
|
||||
"engineBrake": 0.5,
|
||||
"engineBrake": 0.0,
|
||||
"maxPitchRollAngleDeg": 60.0,
|
||||
"maxTorque": 300.0,
|
||||
"rearSteer": false,
|
||||
|
||||
@@ -26,6 +26,9 @@ const float EXIT_GROUND_PROBE_DOWN = 2.0f;
|
||||
* exiting needs a deliberately longer hold than entering. */
|
||||
const float EXIT_MIN_SEAT_TIME = 0.75f;
|
||||
const float EXIT_HOLD_TIME = 0.5f;
|
||||
/* Stepping out of a stopped or nearly stopped vehicle parks it
|
||||
* (handbrake); bailing out at speed lets the car coast on. */
|
||||
const float EXIT_PARK_MAX_SPEED = 1.0f;
|
||||
}
|
||||
|
||||
VehicleControllerSystem::VehicleControllerSystem(flecs::world &world,
|
||||
@@ -372,11 +375,12 @@ void VehicleControllerSystem::update(float deltaTime)
|
||||
/* Fork lift: R raises, F lowers (hold). */
|
||||
vc.inputFork = input.r ? 1.0f : (input.f ? -1.0f : 0.0f);
|
||||
} else {
|
||||
/* Passenger seat: no driving inputs reach the vehicle. */
|
||||
/* Passenger seat: no driving inputs reach the vehicle.
|
||||
* The handbrake is left alone — it is part of the
|
||||
* parked state set when the driver got out. */
|
||||
vc.inputForward = 0.0f;
|
||||
vc.inputRight = 0.0f;
|
||||
vc.inputBrake = 0.0f;
|
||||
vc.inputHandBrake = 0.0f;
|
||||
vc.inputFork = 0.0f;
|
||||
}
|
||||
|
||||
@@ -427,14 +431,26 @@ bool VehicleControllerSystem::findExitSpot(flecs::entity vehicle,
|
||||
/* Exit through the door of the seat actually occupied: sideways
|
||||
* from the seat's own row (chassis +X is the left/driver side),
|
||||
* then the opposite side of the same row, then behind/ahead of
|
||||
* the vehicle as fallbacks. */
|
||||
* the vehicle as fallbacks. The direction vectors are flattened:
|
||||
* a tilted car (slope, curb) would otherwise swing the spots
|
||||
* meters up/down and the ground probe would miss real ground. */
|
||||
float side = seatOffset.x >= 0.0f ? 1.0f : -1.0f;
|
||||
Ogre::Vector3 seatWorld = vor * seatOffset;
|
||||
Ogre::Vector3 rightFlat = right;
|
||||
rightFlat.y = 0.0f;
|
||||
if (rightFlat.squaredLength() < 0.01f)
|
||||
rightFlat = Ogre::Vector3::UNIT_X;
|
||||
rightFlat.normalise();
|
||||
Ogre::Vector3 fwdFlat = fwd;
|
||||
fwdFlat.y = 0.0f;
|
||||
if (fwdFlat.squaredLength() < 0.01f)
|
||||
fwdFlat = Ogre::Vector3::UNIT_Z;
|
||||
fwdFlat.normalise();
|
||||
const Ogre::Vector3 candidates[4] = {
|
||||
seatWorld + right * (side * EXIT_SIDE_OFFSET),
|
||||
seatWorld - right * (side * EXIT_SIDE_OFFSET),
|
||||
vpos - fwd * EXIT_END_OFFSET,
|
||||
vpos + fwd * EXIT_END_OFFSET,
|
||||
seatWorld + rightFlat * (side * EXIT_SIDE_OFFSET),
|
||||
seatWorld - rightFlat * (side * EXIT_SIDE_OFFSET),
|
||||
fwdFlat * -EXIT_END_OFFSET,
|
||||
fwdFlat * EXIT_END_OFFSET,
|
||||
};
|
||||
|
||||
JoltPhysicsWrapper *physics = JoltPhysicsWrapper::getSingletonPtr();
|
||||
@@ -448,18 +464,25 @@ bool VehicleControllerSystem::findExitSpot(flecs::entity vehicle,
|
||||
* vehicle chassis (MOVING) never blocks its own exit. */
|
||||
Ogre::Vector3 hitPos;
|
||||
JPH::BodyID hitBody;
|
||||
Ogre::Vector3 eye = vpos +
|
||||
Ogre::Vector3(0, EXIT_PROBE_HEIGHT, 0);
|
||||
Ogre::Vector3 probe = spot +
|
||||
Ogre::Vector3(0, EXIT_PROBE_HEIGHT, 0);
|
||||
if (physics->raycastQuery(eye, probe, hitPos, hitBody))
|
||||
continue; /* wall/terrain between vehicle and spot */
|
||||
Ogre::Vector3 top = spot +
|
||||
Ogre::Vector3(0, EXIT_GROUND_PROBE_UP, 0);
|
||||
Ogre::Vector3 bottom =
|
||||
spot - Ogre::Vector3(0, EXIT_GROUND_PROBE_DOWN, 0);
|
||||
if (!physics->raycastQuery(top, bottom, hitPos, hitBody))
|
||||
continue; /* no ground under the spot */
|
||||
/* Clearance ray from above the vehicle to just above the
|
||||
* GROUND at the spot — using the spot's ground height keeps
|
||||
* a gentle slope from swallowing the ray (a straight
|
||||
* chassis-height ray dips into an uphill slope and would
|
||||
* reject a perfectly good door-side spot). Walls still
|
||||
* block it. */
|
||||
Ogre::Vector3 eye = vpos +
|
||||
Ogre::Vector3(0, EXIT_PROBE_HEIGHT, 0);
|
||||
Ogre::Vector3 probe = hitPos +
|
||||
Ogre::Vector3(0, EXIT_PROBE_HEIGHT, 0);
|
||||
Ogre::Vector3 wallHit;
|
||||
if (physics->raycastQuery(eye, probe, wallHit, hitBody))
|
||||
continue; /* wall between vehicle and spot */
|
||||
out = hitPos + Ogre::Vector3(0, 0.05f, 0);
|
||||
return true;
|
||||
}
|
||||
@@ -544,6 +567,18 @@ void VehicleControllerSystem::exitVehicle()
|
||||
vc.inputBrake = 0.0f;
|
||||
vc.inputHandBrake = 0.0f;
|
||||
vc.inputFork = 0.0f;
|
||||
/* Park a stopped or nearly stopped vehicle (handbrake) so it does
|
||||
* not roll away on a slope; bailing out at speed lets the car
|
||||
* coast on. The handbrake input persists until someone seats a
|
||||
* driver again (update() overwrites the inputs every frame). */
|
||||
if (vc.constraintCreated) {
|
||||
JoltPhysicsWrapper *physics =
|
||||
JoltPhysicsWrapper::getSingletonPtr();
|
||||
if (physics &&
|
||||
fabsf(physics->getVehicleForwardSpeed(
|
||||
vc.constraint)) <= EXIT_PARK_MAX_SPEED)
|
||||
vc.inputHandBrake = 1.0f;
|
||||
}
|
||||
if (!vc.seats.empty() && m_seatIndex >= 0 &&
|
||||
m_seatIndex < (int)vc.seats.size())
|
||||
vc.seats[m_seatIndex].occupant = 0;
|
||||
|
||||
@@ -40,7 +40,9 @@ class EditorApp;
|
||||
* actually occupied (then the opposite side of the same row, then
|
||||
* behind/ahead of the vehicle), re-enables its physics and restores
|
||||
* the on-foot camera. When no spot is clear the player stays seated and
|
||||
* a warning is logged.
|
||||
* a warning is logged. Exiting a stopped or nearly stopped vehicle
|
||||
* parks it (the handbrake input is left engaged until a driver seats
|
||||
* again); bailing out at speed lets the car coast on.
|
||||
*
|
||||
* Driving state is runtime-only: it does not survive save/load, and
|
||||
* resetDriving() (called from EditorApp::destroySceneEntities) drops it
|
||||
|
||||
Reference in New Issue
Block a user