Taxi demo update with sedan

This commit is contained in:
2026-09-17 02:55:53 +03:00
parent c748a7dd1e
commit 3c7f50a4ef
21 changed files with 2007 additions and 105 deletions
+109 -26
View File
@@ -264,8 +264,49 @@ cd demos/demo-taxi
./demoTaxi --headless --exit-after-first-frame
# ...or headless end-to-end check of the inherited scene contract:
./demoTaxi --headless --test-switch
# Controls: mouse = look, W/A/S/D = move, Shift = run, E = use door,
# Escape = pause menu (frees the cursor).
# ...or headless sedan drive test (spawn, enter, drive, steer, exit —
# plus a passenger-seat leg: no PlayerControlledComponent, W does not
# drive, and the exit must land on the occupied seat's side of the car)
# on the flat vehicle test scene:
./demoTaxi --headless --test-vehicle-drive demo_scene_vehicletest.json
# ...same test on the exterior scene (streaming terrain, sedan_taxi):
./demoTaxi --headless --test-vehicle-drive
# ...or the quantitative handling check (W to ~15 m/s, then full lock
# for 150 frames; PASS needs >= 45 deg of yaw - catches the
# "drives like it's on ice" regression):
./demoTaxi --headless --test-vehicle-turn
# 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
# raycast legitimately fails.
# Controls: mouse = look, W/A/S/D = move, Shift = run, E = use door /
# sit in the seat of the sedan door you used (driver = front-left,
# only the driver seat drives), hold E = exit the vehicle, Space =
# 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
# 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
# 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).
# 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
# --force-pos x y z yawDeg [pitchDeg] teleport the player once,
# re-apply the TPS yaw/pitch
# --debug-enter-vehicle name seat the player in the named
# vehicle (default driver seat)
# --debug-enter-seat seat pick the seat (driver,
# passenger, rl, rr)
# --screenshot N path dump frame N and quit
# Road wedge/segment self-intersection regression test (no scene needed,
@@ -988,13 +1029,38 @@ collider) gets a vehicle constraint on its rigid body;
/ `inputBrake` / `inputHandBrake` into Jolt (with the Jolt sample's
brake-before-reverse rule) and `postPhysicsUpdate()` moves wheel visual
child nodes (created from `wheelMeshName`) to the constraint's wheel
transforms. Both hooks are called from `EditorApp` around the physics
step. Any number of vehicles per scene; removal of the component or
transforms — right-side wheels (chassis -X) get an extra 180 deg flip
about the local forward axis, because Jolt reports every wheel with the
same axle direction and a single shared wheel mesh would otherwise show
its inner face outward on the right (no mirrored mesh needed). Both
hooks are called from `EditorApp` around the physics step. Any number of vehicles per scene; removal of the component or
the body tears the constraint down safely.
`prePhysicsUpdate()` also applies anti-two-wheel righting: when the
chassis tilt passes ~35 deg (up to ~80 deg — a car on its side or
roof is left alone) the wrapper's `addTorque` applies a torque about
the chassis-up x world-up axis proportional to sin(tilt), and above
~5 deg of tilt the angular velocity along that axis is damped so the
car does not rock on its suspension for seconds. A high-grip car
still trips past 45 deg transiently in a full-lock turn, but it can
no longer get stuck driving on two wheels; regression check:
`demoTaxi --headless --test-vehicle-righting`.
`VehicleComponent` fields (serialized as the `vehicle` scene key,
editable via `ui/VehicleEditor`): `maxTorque`, `maxPitchRollAngleDeg`,
`seatOffset`, `cameraDistance` / `cameraHeight` (TPS boom while driving),
`tireFrictionLongitudinal` / `tireFrictionLateral` (multipliers over
the Jolt default tire friction curves, peak 1.2, before Jolt combines
them with ground friction via sqrt(tire * ground) — 1.0 drives like
wet ice at speed, sedans use 1.3 / 3.0), `antiRollBarStiffness`
(N/m per axle pair of Jolt VehicleAntiRollBar, 0 = none; sedans use
2000),
`seats` (a list of named chassis-space seats, each with `name`,
`offset` and a `driver` flag — only driver seats get driving controls;
when empty the legacy `seatOffset` acts as the single driver seat),
`seatCharacterScale` (uniform scale applied to the character node
while seated, restored on exit — < 1 shrinks the stock chair-sit pose
to fit a car cabin), `cameraDistance` / `cameraHeight` (TPS boom while
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 —
@@ -1160,37 +1226,54 @@ without one).
Game-mode-only system (`systems/VehicleControllerSystem.{hpp,cpp}`) that
makes vehicles drivable by the player:
- **Enter**: an actuator action (e.g. `vehicle_drive` in the sokoban demo)
runs a `luaTask` node `vehicleEnter` (registered in
`lua-scripts/data2.lua`) which sends the `vehicle_enter` event with a
`vehicle=<entity name>` param; the system subscribes on the `EventBus`
(`enterVehicle(entity)` is also directly callable by tests). Entering
disables the player character's physics capsule
(`CharacterSystem::disablePhysics`), pins its node to
`VehicleComponent::seatOffset` (re-pinned every frame from
- **Enter**: an actuator action (e.g. `vehicle_drive` in the sokoban demo,
`vehicle_seat_*` in the taxi demo) runs a `luaTask` node `vehicleEnter`
(registered in `lua-scripts/data2.lua`) which sends the `vehicle_enter`
event with a `vehicle=<entity name>` param (empty = nearest vehicle
within 5 m of the player) and an optional `seat=<seat name>` param
(empty = driver seat, else first free seat); the system subscribes on
the `EventBus` (`enterVehicle(entity)` is also directly callable by
tests). Entering disables the player character's physics capsule
(`CharacterSystem::disablePhysics`), pins its node to the resolved
seat's offset (`VehicleComponent::seats`, or the legacy single driver
`seatOffset` when `seats` is empty; re-pinned every frame from
`postPhysicsUpdate()`, after the physics step syncs the vehicle node —
see "Frame Update Order" 21a), parks the locomotion state
machine in idle, tags the vehicle `PlayerControlledComponent`, sets
`PlayerControllerComponent::driving` and swaps `tpsDistance`/`tpsHeight`
for the vehicle's `cameraDistance`/`cameraHeight`. The character plays
see "Frame Update Order" 21a), applies `seatCharacterScale` to the
character node (restored on exit), parks the locomotion state
machine in idle, marks the seat occupied, and — only on a driver
seat — tags the vehicle `PlayerControlledComponent`, sets
`PlayerControllerComponent::driving` and swaps
`tpsDistance`/`tpsHeight` for the vehicle's
`cameraDistance`/`cameraHeight`. The character plays
the locomotion state machine's `PlayerControllerComponent::drivingState`
state ("driving", mapped to the `sitting` animation in the demo's
animation_tree.json); `idleState` is restored on exit. Entering also
state ("driving", mapped to the `sitting` animation in the sokoban
demo's animation_tree.json, `sitting-vehicle` in the taxi demo's);
`idleState` is restored on exit. Entering also
pushes a controls hint to the GameHudSystem ("W/S move, A/D steer, hold
E to exit", plus "R/F fork up/down" when the vehicle has a fork).
E to exit", plus "R/F fork up/down" when the vehicle has a fork;
passengers get "Riding: hold E to exit").
- **Driving**: `update()` (called right before `PlayerControllerSystem` in
Game+Playing) feeds `GameInputState` into the vehicle inputs — W/S
`inputForward`, A/D `inputRight`, Space `inputHandBrake`.
`inputForward`, A/D `inputRight`, Space `inputHandBrake` — only when
the player sits on a driver seat; passengers zero the inputs.
- **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 (±X) or behind/ahead (±Z) of the chassis
(horizontal clearance ray + downward ground ray; `raycastQuery` sees
NON_MOVING only, so the vehicle never blocks its own exit), moves the
raycast-clear spot beside the door of the seat actually occupied
(sideways from the seat's own chassis-space row, 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
character node there, re-enables physics (the CharacterSystem diff check
teleports the capsule to the node) and restores the on-foot camera
settings. No clear spot → the player stays seated and a warning is
logged.
settings. 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
(EditorApp::keyPressed), which previously re-fired the door actuators
during a long hold. No clear spot → the player stays seated and a
warning is logged.
- **Teardown**: `resetDriving()` (no raycasts) runs from
`EditorApp::destroySceneEntities` before `resetControllers()`, and the
system is destroyed before `PlayerControllerSystem`/`CharacterSystem`.
+8 -4
View File
@@ -2764,17 +2764,20 @@ bool EditorApp::keyPressed(const OgreBites::KeyboardEvent &evt)
case 'e':
case 'E':
m_gameInput.e = true;
m_gameInput.ePressed = true;
if (!isRepeat)
m_gameInput.ePressed = true;
break;
case 'f':
case 'F':
m_gameInput.f = true;
m_gameInput.fPressed = true;
if (!isRepeat)
m_gameInput.fPressed = true;
break;
case 'i':
case 'I':
m_gameInput.i = true;
m_gameInput.iPressed = true;
if (!isRepeat)
m_gameInput.iPressed = true;
break;
case 'r':
case 'R':
@@ -2785,7 +2788,8 @@ bool EditorApp::keyPressed(const OgreBites::KeyboardEvent &evt)
break;
case OgreBites::SDLK_SPACE:
m_gameInput.space = true;
m_gameInput.spacePressed = true;
if (!isRepeat)
m_gameInput.spacePressed = true;
break;
}
return true;
+44 -1
View File
@@ -34,6 +34,24 @@ struct VehicleWheel {
float maxHandBrakeTorque = 0.0f;
};
/**
* Vehicle seat definition.
*
* Seats are named ("driver", "passenger", ...) so scene actions can ask
* for a specific one; the driver flag marks the seat that gets driving
* controls (player-controlled driving only happens there, NPC passengers
* sit in the others).
*/
struct VehicleSeat {
Ogre::String name = "driver";
/* Seat offset in chassis space (character root, sitting pose). */
Ogre::Vector3 offset = Ogre::Vector3(0.0f, 1.0f, 0.0f);
bool driver = false;
/* Runtime: entity currently sitting here (0 = free). */
uint64_t occupant = 0;
};
/**
* Vehicle component.
*
@@ -52,11 +70,36 @@ struct VehicleComponent {
/* Degrees; 0 = unlimited. */
float maxPitchRollAngleDeg = 60.0f;
/* Tire friction multipliers: scale the Jolt default tire friction
* curves (peak ~1.2) before they are combined with the ground
* friction. 1.0 = stock asphalt tire; raise lateral for less
* drift-prone arcade handling (sedans use ~1.8). */
float tireFrictionLongitudinal = 1.0f;
float tireFrictionLateral = 1.0f;
/* Anti-roll bar stiffness (N/m) per axle pair (Jolt
* VehicleAntiRollBar; 0 = none). Limits body roll in hard
* corners so a soft-sprung car does not end up on two wheels. */
float antiRollBarStiffness = 0.0f;
/* Driver seat offset in chassis space (used by the
* VehicleControllerSystem enter/exit flow to seat the player
* character). */
* character). Legacy single-seat shortcut: only used when seats
* is empty, where it acts as the single driver seat. */
Ogre::Vector3 seatOffset = Ogre::Vector3(0.0f, 1.0f, 0.0f);
/* All seats of the vehicle (chassis space). When empty, the
* vehicle has one implicit driver seat at seatOffset. */
std::vector<VehicleSeat> seats;
/* Uniform scale applied to the character node while seated
* (restored on exit). The stock sit animations are proportioned
* for a chair, not a car cabin: with a normal sedan cabin the feet
* dangle below the rocker line and the head grazes the roof, so
* shrinking the seated character a little (0.85) makes the pose
* fit without cartoonish seat geometry. 1.0 = no scaling. */
float seatCharacterScale = 1.0f;
/* Rear-wheel steering (like a real forklift): flips the steer sign
* so D still turns the nose right — with the steered axle behind
* the driver the raw wheel angle would turn the nose left. */
+4 -3
View File
@@ -7,10 +7,11 @@ ecs.behavior_tree.register_node("luaHello", function(entity_id, params)
end)
-- F10 vehicles: seat the player character on the named vehicle entity
-- (VehicleControllerSystem listens for "vehicle_enter").
-- (VehicleControllerSystem listens for "vehicle_enter"). "vehicle" is
-- optional (empty = nearest vehicle to the player); "seat" picks a seat
-- by name (empty = driver seat / first free seat).
ecs.behavior_tree.register_node("vehicleEnter", function(entity_id, params)
local vehicle = params.vehicle or ""
ecs.send_event("vehicle_enter", { vehicle = vehicle })
ecs.send_event("vehicle_enter", { vehicle = params.vehicle or "", seat = params.seat or "" })
return "success"
end)
@@ -650,9 +650,32 @@ public:
w->mMaxHandBrakeTorque = wd.maxHandBrakeTorque;
w->mRadius = wd.radius;
w->mWidth = wd.width;
/* Scale the default tire friction curves (see the
* WheelSettingsWV constructor) by the requested
* multipliers; Jolt combines them with the ground
* friction via sqrt(tire * ground). */
for (JPH::LinearCurve::Point &p :
w->mLongitudinalFriction.mPoints)
p.mY *= desc.tireFrictionLongitudinal;
for (JPH::LinearCurve::Point &p :
w->mLateralFriction.mPoints)
p.mY *= desc.tireFrictionLateral;
settings.mWheels.push_back(w);
}
/* One anti-roll bar per axle pair (wheels 0/1, 2/3, ...) to
* limit body roll in corners. */
if (desc.antiRollBarStiffness > 0.0f) {
settings.mAntiRollBars.resize(desc.wheels.size() / 2);
for (size_t i = 0; i + 1 < desc.wheels.size(); i += 2) {
JPH::VehicleAntiRollBar &bar =
settings.mAntiRollBars[i / 2];
bar.mLeftWheel = (int)i;
bar.mRightWheel = (int)i + 1;
bar.mStiffness = desc.antiRollBarStiffness;
}
}
JPH::WheeledVehicleControllerSettings *controller =
new JPH::WheeledVehicleControllerSettings;
settings.mController = controller;
@@ -1793,6 +1816,12 @@ public:
physics_system.GetBodyInterface().GetAngularVelocity(
id));
}
void addTorque(JPH::BodyID id, const Ogre::Vector3 &torque)
{
physics_system.GetBodyInterface().AddTorque(
id, JPH::Vec3(torque.x, torque.y, torque.z),
JPH::EActivation::Activate);
}
void setVelocities(JPH::BodyID id, const Ogre::Vector3 &linear,
const Ogre::Vector3 &angular)
{
@@ -2219,6 +2248,11 @@ Ogre::Vector3 JoltPhysicsWrapper::getAngularVelocity(JPH::BodyID id)
{
return phys->getAngularVelocity(id);
}
void JoltPhysicsWrapper::addTorque(JPH::BodyID id,
const Ogre::Vector3 &torque)
{
phys->addTorque(id, torque);
}
void JoltPhysicsWrapper::setVelocities(JPH::BodyID id,
const Ogre::Vector3 &linear,
const Ogre::Vector3 &angular)
+7
View File
@@ -232,6 +232,7 @@ public:
bool activate = true);
Ogre::Vector3 getLinearVelocity(JPH::BodyID id);
Ogre::Vector3 getAngularVelocity(JPH::BodyID id);
void addTorque(JPH::BodyID id, const Ogre::Vector3 &torque);
void setVelocities(JPH::BodyID id, const Ogre::Vector3 &linear,
const Ogre::Vector3 &angular);
float getFriction(JPH::BodyID id);
@@ -307,6 +308,12 @@ public:
float maxTorque = 400.0f;
/* Degrees; 0 = unlimited. */
float maxPitchRollAngleDeg = 60.0f;
/* Multipliers for the default tire friction curves
* (1.0 = stock asphalt tire). */
float tireFrictionLongitudinal = 1.0f;
float tireFrictionLateral = 1.0f;
/* Anti-roll bar stiffness (N/m) per axle pair; 0 = none. */
float antiRollBarStiffness = 0.0f;
};
/* Create/destroy a vehicle on an existing (already added) dynamic
@@ -549,6 +549,22 @@ void AnimationTreeSystem::update(float deltaTime)
}
}
/* Disable animations that were not evaluated this frame.
* When a fading-out animation's weight drops to zero in a
* single step (large deltaTime * fadeSpeed), evaluateNode
* skips it entirely and it never appears in ctx.animData;
* without this, its Ogre::AnimationState would stay enabled
* forever at its last non-zero weight with frozen time. */
for (auto &pair : state.animations) {
Ogre::AnimationState *as = pair.second.ogreAnimState;
if (!as || !as->getEnabled())
continue;
if (ctx.animData.find(pair.first) == ctx.animData.end()) {
as->setEnabled(false);
as->setWeight(0.0f);
}
}
if (at.useRootMotion && sceneNode) {
/* Smooth root-motion displacement with EMA to
* filter high-frequency hip oscillation within
@@ -1669,9 +1669,30 @@ nlohmann::json SceneSerializer::serializeVehicle(flecs::entity entity)
json["maxTorque"] = vehicle.maxTorque;
json["maxPitchRollAngleDeg"] = vehicle.maxPitchRollAngleDeg;
if (vehicle.tireFrictionLongitudinal != 1.0f)
json["tireFrictionLongitudinal"] =
vehicle.tireFrictionLongitudinal;
if (vehicle.tireFrictionLateral != 1.0f)
json["tireFrictionLateral"] = vehicle.tireFrictionLateral;
if (vehicle.antiRollBarStiffness != 0.0f)
json["antiRollBarStiffness"] = vehicle.antiRollBarStiffness;
json["seatOffset"] = { { "x", vehicle.seatOffset.x },
{ "y", vehicle.seatOffset.y },
{ "z", vehicle.seatOffset.z } };
nlohmann::json seats = nlohmann::json::array();
for (const VehicleSeat &s : vehicle.seats) {
nlohmann::json sj;
sj["name"] = s.name;
sj["offset"] = { { "x", s.offset.x },
{ "y", s.offset.y },
{ "z", s.offset.z } };
sj["driver"] = s.driver;
seats.push_back(sj);
}
if (!vehicle.seats.empty())
json["seats"] = seats;
if (vehicle.seatCharacterScale != 1.0f)
json["seatCharacterScale"] = vehicle.seatCharacterScale;
json["wheelMeshName"] = vehicle.wheelMeshName;
json["steeringWheelMeshName"] = vehicle.steeringWheelMeshName;
json["steeringWheelOffset"] = {
@@ -1721,12 +1742,33 @@ void SceneSerializer::deserializeVehicle(flecs::entity entity,
vehicle.maxTorque = json.value("maxTorque", 400.0f);
vehicle.maxPitchRollAngleDeg =
json.value("maxPitchRollAngleDeg", 60.0f);
vehicle.tireFrictionLongitudinal =
json.value("tireFrictionLongitudinal", 1.0f);
vehicle.tireFrictionLateral =
json.value("tireFrictionLateral", 1.0f);
vehicle.antiRollBarStiffness =
json.value("antiRollBarStiffness", 0.0f);
if (json.contains("seatOffset")) {
auto &so = json["seatOffset"];
vehicle.seatOffset = Ogre::Vector3(so.value("x", 0.0f),
so.value("y", 1.0f),
so.value("z", 0.0f));
}
if (json.contains("seats")) {
for (const auto &sj : json["seats"]) {
VehicleSeat s;
s.name = sj.value("name", "driver");
if (sj.contains("offset")) {
auto &o = sj["offset"];
s.offset = Ogre::Vector3(o.value("x", 0.0f),
o.value("y", 1.0f),
o.value("z", 0.0f));
}
s.driver = sj.value("driver", false);
vehicle.seats.push_back(s);
}
}
vehicle.seatCharacterScale = json.value("seatCharacterScale", 1.0f);
vehicle.wheelMeshName = json.value("wheelMeshName", "");
vehicle.steeringWheelMeshName =
json.value("steeringWheelMeshName", "");
@@ -67,40 +67,138 @@ flecs::entity VehicleControllerSystem::findPlayerController()
return result;
}
flecs::entity VehicleControllerSystem::findNearestVehicle(
const Ogre::Vector3 &pos, float maxDist) const
{
flecs::entity result = flecs::entity::null();
float best = maxDist;
m_world.query<VehicleComponent, TransformComponent>().each(
[&](flecs::entity e, VehicleComponent &, TransformComponent &t) {
if (!t.node)
return;
float d = t.node->_getDerivedPosition().distance(pos);
if (d < best) {
best = d;
result = e;
}
});
return result;
}
void VehicleControllerSystem::onVehicleEnterEvent(
const Ogre::String &eventName, const editScene::EventParams &params)
{
(void)eventName;
if (m_driving)
return;
/* After an exit, E must have been released once before it can
* seat the player again (the exit is a hold, so the key is still
* down when the exit spot lands next to a door actuator). */
if (m_eReleaseRequired)
return;
Ogre::String vehicleName = params.getString("vehicle", "");
if (vehicleName.empty()) {
Ogre::LogManager::getSingleton().logMessage(
"[VehicleControllerSystem] vehicle_enter event without "
"a 'vehicle' param, ignored");
return;
}
Ogre::String seatName = params.getString("seat", "");
flecs::entity vehicle = flecs::entity::null();
m_world.query<EntityNameComponent>().each(
[&](flecs::entity e, EntityNameComponent &name) {
if (vehicle.is_alive())
return;
if (name.name == vehicleName)
vehicle = e;
});
if (!vehicle.is_alive()) {
Ogre::LogManager::getSingleton().logMessage(
"[VehicleControllerSystem] vehicle_enter: no entity "
"named '" +
vehicleName + "'");
return;
if (vehicleName.empty()) {
/* No vehicle named: take the nearest one to the player
* character (per-door seat actuators are generic and rely
* on this). */
flecs::entity character =
m_editorApp->getPlayerCharacterEntity();
if (character.is_alive() &&
character.has<TransformComponent>() &&
character.get<TransformComponent>().node) {
vehicle = findNearestVehicle(
character.get<TransformComponent>()
.node->_getDerivedPosition(),
5.0f);
}
if (!vehicle.is_alive()) {
Ogre::LogManager::getSingleton().logMessage(
"[VehicleControllerSystem] vehicle_enter: no "
"'vehicle' param and no vehicle near the player");
return;
}
} else {
m_world.query<EntityNameComponent>().each(
[&](flecs::entity e, EntityNameComponent &name) {
if (vehicle.is_alive())
return;
if (name.name == vehicleName)
vehicle = e;
});
if (!vehicle.is_alive()) {
Ogre::LogManager::getSingleton().logMessage(
"[VehicleControllerSystem] vehicle_enter: no entity "
"named '" +
vehicleName + "'");
return;
}
}
enterVehicle(vehicle);
enterVehicle(vehicle, seatName);
}
int VehicleControllerSystem::seatCount(const VehicleComponent &vc) const
{
return vc.seats.empty() ? 1 : (int)vc.seats.size();
}
Ogre::Vector3 VehicleControllerSystem::seatOffsetAt(
const VehicleComponent &vc, int index) const
{
if (vc.seats.empty())
return vc.seatOffset;
return vc.seats[index].offset;
}
bool VehicleControllerSystem::seatIsDriverAt(const VehicleComponent &vc,
int index) const
{
if (vc.seats.empty())
return true; /* the implicit seatOffset seat is the driver */
return vc.seats[index].driver;
}
bool VehicleControllerSystem::seatIsFreeAt(const VehicleComponent &vc,
int index) const
{
if (vc.seats.empty())
return true; /* single player, implicit seat: untracked */
return vc.seats[index].occupant == 0;
}
int VehicleControllerSystem::resolveSeat(const VehicleComponent &vc,
const Ogre::String &requested) const
{
int count = seatCount(vc);
if (!requested.empty()) {
for (int i = 0; i < count; i++) {
Ogre::String name = vc.seats.empty() ?
"driver" :
vc.seats[i].name;
if (name == requested && seatIsFreeAt(vc, i))
return i;
}
}
/* Fall back to the driver seat, then to any free seat. */
for (int i = 0; i < count; i++)
if (seatIsDriverAt(vc, i) && seatIsFreeAt(vc, i))
return i;
for (int i = 0; i < count; i++)
if (seatIsFreeAt(vc, i))
return i;
return -1;
}
bool VehicleControllerSystem::enterVehicle(flecs::entity vehicle)
{
return enterVehicle(vehicle, "");
}
bool VehicleControllerSystem::enterVehicle(flecs::entity vehicle,
const Ogre::String &seatName)
{
if (m_driving)
return false;
@@ -126,24 +224,59 @@ bool VehicleControllerSystem::enterVehicle(flecs::entity vehicle)
return false;
auto &pc = controller.get_mut<PlayerControllerComponent>();
/* Pick the seat: the requested one when free, else the driver
* seat, else any free seat. */
const VehicleComponent &vcc = vehicle.get<VehicleComponent>();
int seatIndex = resolveSeat(vcc, seatName);
if (seatIndex < 0) {
GameHudSystem *hud = m_editorApp->getGameHudSystem();
if (hud)
hud->pushMessage("No free seat in this vehicle",
4.0f);
return false;
}
m_savedTpsDistance = pc.tpsDistance;
m_savedTpsHeight = pc.tpsHeight;
pc.driving = true;
pc.tpsDistance = vehicle.get<VehicleComponent>().cameraDistance;
pc.tpsHeight = vehicle.get<VehicleComponent>().cameraHeight;
pc.tpsDistance = vcc.cameraDistance;
pc.tpsHeight = vcc.cameraHeight;
CharacterSystem *cs = m_editorApp->getCharacterSystem();
if (cs)
cs->disablePhysics(character);
/* Switch to the driving animation state (currently mapped to the
* "sitting" animation in the locomotion state machine). */
/* Switch to the driving animation state (the animation it maps
* to comes from the scene's animation tree; demo-taxi maps it to
* "sitting-vehicle"). */
AnimationTreeSystem *ats = m_editorApp->getAnimationTreeSystem();
if (ats)
ats->setState(character, pc.locomotionStateMachine,
pc.drivingState);
vehicle.add<PlayerControlledComponent>();
m_seatIndex = seatIndex;
m_seatIsDriver = seatIsDriverAt(vcc, seatIndex);
if (!vcc.seats.empty()) {
auto &vcm = vehicle.get_mut<VehicleComponent>();
vcm.seats[seatIndex].occupant = character.id();
}
/* Fit the character into the cabin (seatCharacterScale; restored
* by restoreOnFoot). */
{
auto &ctm = character.get_mut<TransformComponent>();
if (ctm.node) {
m_savedCharScale = ctm.node->getScale();
if (vcc.seatCharacterScale != 1.0f)
ctm.node->setScale(Ogre::Vector3(
vcc.seatCharacterScale));
}
}
/* Only the driver seat marks the vehicle player-controlled and
* gets the driving inputs; passengers just sit. */
if (m_seatIsDriver)
vehicle.add<PlayerControlledComponent>();
m_driving = true;
m_vehicleId = vehicle.id();
@@ -158,11 +291,13 @@ bool VehicleControllerSystem::enterVehicle(flecs::entity vehicle)
* actually has a fork). */
GameHudSystem *hud = m_editorApp->getGameHudSystem();
if (hud) {
Ogre::String hint =
"Driving: W/S move, A/D steer, hold E to exit";
if (!vehicle.get<VehicleComponent>().forkMeshName.empty())
hint = "Driving: W/S move, A/D steer, R/F fork "
"up/down, hold E to exit";
Ogre::String hint = "Riding: hold E to exit";
if (m_seatIsDriver) {
hint = "Driving: W/S move, A/D steer, hold E to exit";
if (!vcc.forkMeshName.empty())
hint = "Driving: W/S move, A/D steer, R/F fork "
"up/down, hold E to exit";
}
hud->pushMessage(hint, 10.0f);
}
@@ -184,7 +319,7 @@ void VehicleControllerSystem::pinCharacterToSeat(flecs::entity vehicle,
* uses setPosition), so derived == world here; vehicle scale is
* 1 in practice. */
Ogre::Vector3 seat = vt.node->_getDerivedOrientation() *
vc.seatOffset +
seatOffsetAt(vc, m_seatIndex) +
vt.node->_getDerivedPosition();
ct.node->setPosition(seat);
ct.node->setOrientation(vt.node->_getDerivedOrientation());
@@ -193,6 +328,9 @@ void VehicleControllerSystem::pinCharacterToSeat(flecs::entity vehicle,
void VehicleControllerSystem::update(float deltaTime)
{
if (m_eReleaseRequired && !m_editorApp->getGameInputState().e)
m_eReleaseRequired = false;
if (!m_driving)
return;
@@ -209,6 +347,8 @@ void VehicleControllerSystem::update(float deltaTime)
m_vehicleId = 0;
m_characterId = 0;
m_controllerId = 0;
m_seatIndex = -1;
m_seatIsDriver = false;
return;
}
if (!character.is_alive()) {
@@ -217,17 +357,28 @@ void VehicleControllerSystem::update(float deltaTime)
m_vehicleId = 0;
m_characterId = 0;
m_controllerId = 0;
m_seatIndex = -1;
m_seatIsDriver = false;
return;
}
GameInputState &input = m_editorApp->getGameInputState();
auto &vc = vehicle.get_mut<VehicleComponent>();
vc.inputForward = input.w ? 1.0f : (input.s ? -1.0f : 0.0f);
vc.inputRight = input.d ? 1.0f : (input.a ? -1.0f : 0.0f);
vc.inputBrake = 0.0f;
vc.inputHandBrake = input.space ? 1.0f : 0.0f;
/* Fork lift: R raises, F lowers (hold). */
vc.inputFork = input.r ? 1.0f : (input.f ? -1.0f : 0.0f);
if (m_seatIsDriver) {
vc.inputForward = input.w ? 1.0f : (input.s ? -1.0f : 0.0f);
vc.inputRight = input.d ? 1.0f : (input.a ? -1.0f : 0.0f);
vc.inputBrake = 0.0f;
vc.inputHandBrake = input.space ? 1.0f : 0.0f;
/* 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. */
vc.inputForward = 0.0f;
vc.inputRight = 0.0f;
vc.inputBrake = 0.0f;
vc.inputHandBrake = 0.0f;
vc.inputFork = 0.0f;
}
/* The seat pin runs in postPhysicsUpdate() (after the physics
* step syncs the vehicle node) so the driver and the seat are
@@ -261,6 +412,7 @@ void VehicleControllerSystem::postPhysicsUpdate()
}
bool VehicleControllerSystem::findExitSpot(flecs::entity vehicle,
const Ogre::Vector3 &seatOffset,
Ogre::Vector3 &out) const
{
const TransformComponent &vt = vehicle.get<TransformComponent>();
@@ -272,16 +424,22 @@ bool VehicleControllerSystem::findExitSpot(flecs::entity vehicle,
Ogre::Vector3 right = vor * Ogre::Vector3::UNIT_X;
Ogre::Vector3 fwd = vor * Ogre::Vector3::UNIT_Z;
/* 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. */
float side = seatOffset.x >= 0.0f ? 1.0f : -1.0f;
Ogre::Vector3 seatWorld = vor * seatOffset;
const Ogre::Vector3 candidates[4] = {
vpos + right * EXIT_SIDE_OFFSET,
vpos - right * EXIT_SIDE_OFFSET,
seatWorld + right * (side * EXIT_SIDE_OFFSET),
seatWorld - right * (side * EXIT_SIDE_OFFSET),
vpos - fwd * EXIT_END_OFFSET,
vpos + fwd * EXIT_END_OFFSET,
};
JoltPhysicsWrapper *physics = JoltPhysicsWrapper::getSingletonPtr();
for (int i = 0; i < 4; i++) {
Ogre::Vector3 spot = candidates[i];
Ogre::Vector3 spot = vpos + candidates[i];
if (!physics) {
out = spot;
return true;
@@ -317,6 +475,12 @@ void VehicleControllerSystem::restoreOnFoot()
CharacterSystem *cs = m_editorApp->getCharacterSystem();
if (cs)
cs->enablePhysics(character);
if (character.has<TransformComponent>()) {
const TransformComponent &ct =
character.get<TransformComponent>();
if (ct.node)
ct.node->setScale(m_savedCharScale);
}
}
if (controller.is_alive() &&
@@ -350,8 +514,14 @@ void VehicleControllerSystem::exitVehicle()
return;
}
/* The exit spot depends on the seat actually occupied, so grab the
* seat offset before the state below is cleared. */
int seatIndex = m_seatIndex >= 0 ? m_seatIndex : 0;
Ogre::Vector3 seatOffset =
seatOffsetAt(vehicle.get<VehicleComponent>(), seatIndex);
Ogre::Vector3 spot;
if (!findExitSpot(vehicle, spot)) {
if (!findExitSpot(vehicle, seatOffset, spot)) {
Ogre::LogManager::getSingleton().logMessage(
"[VehicleControllerSystem] no clear exit spot, "
"staying seated");
@@ -366,13 +536,17 @@ void VehicleControllerSystem::exitVehicle()
ct.node->setPosition(spot);
ct.position = spot;
/* Zero the driver inputs so the vehicle rolls to a stop. */
/* Zero the driver inputs so the vehicle rolls to a stop, and free
* the seat. */
auto &vc = vehicle.get_mut<VehicleComponent>();
vc.inputForward = 0.0f;
vc.inputRight = 0.0f;
vc.inputBrake = 0.0f;
vc.inputHandBrake = 0.0f;
vc.inputFork = 0.0f;
if (!vc.seats.empty() && m_seatIndex >= 0 &&
m_seatIndex < (int)vc.seats.size())
vc.seats[m_seatIndex].occupant = 0;
vehicle.remove<PlayerControlledComponent>();
restoreOnFoot();
@@ -381,6 +555,9 @@ void VehicleControllerSystem::exitVehicle()
m_vehicleId = 0;
m_characterId = 0;
m_controllerId = 0;
m_seatIndex = -1;
m_seatIsDriver = false;
m_eReleaseRequired = true;
Ogre::LogManager::getSingleton().logMessage(
"[VehicleControllerSystem] exited vehicle");
@@ -400,6 +577,9 @@ void VehicleControllerSystem::resetDriving()
vc.inputBrake = 0.0f;
vc.inputHandBrake = 0.0f;
vc.inputFork = 0.0f;
if (!vc.seats.empty() && m_seatIndex >= 0 &&
m_seatIndex < (int)vc.seats.size())
vc.seats[m_seatIndex].occupant = 0;
}
vehicle.remove<PlayerControlledComponent>();
}
@@ -410,4 +590,6 @@ void VehicleControllerSystem::resetDriving()
m_vehicleId = 0;
m_characterId = 0;
m_controllerId = 0;
m_seatIndex = -1;
m_seatIsDriver = false;
}
@@ -3,6 +3,7 @@
#pragma once
#include "EventBus.hpp"
#include "../components/Vehicle.hpp"
#include <Ogre.h>
#include <flecs.h>
@@ -11,25 +12,33 @@ class EditorApp;
/**
* Vehicle enter/exit + drive input (game mode only, F10).
*
* Entering: a scene action (typically an actuator on the vehicle entity)
* runs a luaTask that sends the "vehicle_enter" event with a "vehicle"
* param naming the vehicle entity; this system listens for that event and
* seats the player character: the character's physics capsule is disabled
* Entering: a scene action (typically an actuator on the vehicle entity,
* or on one of its door children) runs a luaTask that sends the
* "vehicle_enter" event with an optional "vehicle" param naming the
* vehicle entity (empty = nearest vehicle to the player character) and
* an optional "seat" param naming the seat (empty = driver seat, or the
* first free seat); this system listens for that event and seats the
* player character: the character's physics capsule is disabled
* (CharacterSystem::disablePhysics), its scene node is pinned to the
* vehicle's VehicleComponent::seatOffset every frame (after the physics
* step, so the seat never jitters under the driver — see
* postPhysicsUpdate), and the player controller's TPS boom is swapped for
* the vehicle's cameraDistance / cameraHeight (restored on exit).
* seat offset (VehicleComponent::seats, or the legacy single driver
* seatOffset when seats is empty) every frame (after the physics step,
* so the seat never jitters under the driver — see postPhysicsUpdate),
* and the player controller's TPS boom is swapped for the vehicle's
* cameraDistance / cameraHeight (restored on exit).
* PlayerControllerComponent::driving gates off locomotion and actuator
* prompts while seated.
*
* Driving: W/S feed VehicleComponent::inputForward, A/D inputRight,
* Space inputHandBrake; VehicleSystem consumes them in the physics step.
* Only the driver seat (VehicleSeat::driver, or the implicit seatOffset
* seat) gets these inputs and marks the vehicle PlayerControlledComponent;
* passengers sit without any control over the vehicle.
*
* Exit: holding E (a longer press than entering: a short cooldown after
* seating must pass first, then E must be held for a moment) places the
* character on the first raycast-clear spot beside
* (then behind/ahead of) the vehicle, re-enables its physics and restores
* character on the first raycast-clear spot beside the door of the seat
* 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.
*
@@ -61,9 +70,14 @@ public:
}
flecs::entity getDrivenVehicle() const;
/** Seat the player character on the vehicle. Returns false when
* already driving or the entity is not a usable vehicle. */
/** Seat the player character on the vehicle (driver seat by
* default). Returns false when already driving or the entity is
* not a usable vehicle. */
bool enterVehicle(flecs::entity vehicle);
/** Same, but asks for the named seat first (falls back to the
* driver seat, then to any free seat). */
bool enterVehicle(flecs::entity vehicle,
const Ogre::String &seatName);
/** Place the character next to the vehicle and restore on-foot
* controls. No-op when not driving. */
void exitVehicle();
@@ -75,9 +89,20 @@ private:
void onVehicleEnterEvent(const Ogre::String &eventName,
const editScene::EventParams &params);
flecs::entity findPlayerController();
flecs::entity findNearestVehicle(const Ogre::Vector3 &pos,
float maxDist) const;
int seatCount(const VehicleComponent &vc) const;
Ogre::Vector3 seatOffsetAt(const VehicleComponent &vc,
int index) const;
bool seatIsDriverAt(const VehicleComponent &vc, int index) const;
bool seatIsFreeAt(const VehicleComponent &vc, int index) const;
int resolveSeat(const VehicleComponent &vc,
const Ogre::String &requested) const;
void pinCharacterToSeat(flecs::entity vehicle,
flecs::entity character);
bool findExitSpot(flecs::entity vehicle, Ogre::Vector3 &out) const;
bool findExitSpot(flecs::entity vehicle,
const Ogre::Vector3 &seatOffset,
Ogre::Vector3 &out) const;
void restoreOnFoot();
flecs::world &m_world;
@@ -88,12 +113,22 @@ private:
flecs::entity_t m_vehicleId = 0;
flecs::entity_t m_characterId = 0;
flecs::entity_t m_controllerId = 0;
/* Resolved seat: index into VehicleComponent::seats (0 for the
* implicit seatOffset seat) and whether it has driving controls. */
int m_seatIndex = -1;
bool m_seatIsDriver = false;
/* Stashed on-foot camera settings, restored on exit. */
float m_savedTpsDistance = 3.0f;
float m_savedTpsHeight = 2.0f;
/* Stashed character node scale (seatCharacterScale is applied
* while seated), restored on exit. */
Ogre::Vector3 m_savedCharScale = Ogre::Vector3::UNIT_SCALE;
/* Exit debounce: time seated and current E hold duration. */
float m_seatTime = 0.0f;
float m_exitHold = 0.0f;
/* Set on exit: the E key (still held from the exit hold) must be
* released once before a vehicle_enter event is accepted again. */
bool m_eReleaseRequired = false;
};
#endif // EDITSCENE_VEHICLECONTROLLERSYSTEM_HPP
@@ -62,6 +62,9 @@ void VehicleSystem::createConstraint(flecs::entity entity,
JoltPhysicsWrapper::VehicleDesc desc;
desc.maxTorque = vehicle.maxTorque;
desc.maxPitchRollAngleDeg = vehicle.maxPitchRollAngleDeg;
desc.tireFrictionLongitudinal = vehicle.tireFrictionLongitudinal;
desc.tireFrictionLateral = vehicle.tireFrictionLateral;
desc.antiRollBarStiffness = vehicle.antiRollBarStiffness;
desc.wheels.reserve(vehicle.wheels.size());
for (const VehicleWheel &vw : vehicle.wheels) {
JoltPhysicsWrapper::VehicleWheelDesc wd;
@@ -317,7 +320,7 @@ void VehicleSystem::prePhysicsUpdate(float deltaTime)
m_query.each([&](flecs::entity entity, VehicleComponent &vehicle,
RigidBodyComponent &rigidBody,
TransformComponent &) {
TransformComponent &transform) {
/* The chassis body went away (disabled, rebuilt, scene
* teardown): the constraint references the old body, drop
* it. */
@@ -390,6 +393,49 @@ void VehicleSystem::prePhysicsUpdate(float deltaTime)
float blend = std::min(1.0f, deltaTime * 8.0f);
steer += (steerTarget - steer) * blend;
/* Anti-two-wheel righting: a high-grip car can end up
* balanced on two wheels at ~45 deg after a hard turn and
* just stay there. When the chassis tilt is past ~35 deg
* but short of fully rolled (lying on the side/roof is
* fine and settles on its own), apply a torque about the
* (chassis up x world up) axis to put it back on four
* wheels. Above ~5 deg of tilt, also damp the angular
* velocity along the same axis so the car does not rock
* on its soft suspension for seconds afterwards. The
* thresholds are above any sane road slope, so parked
* cars on hills are untouched. */
if (transform.node && rigidBody.bodyCreated) {
Ogre::Vector3 up =
transform.node->_getDerivedOrientation() *
Ogre::Vector3::UNIT_Y;
const float cosDamp = 0.9962f; /* 5 deg */
const float cosMin = 0.8192f; /* 35 deg */
const float cosMax = 0.1736f; /* 80 deg */
if (up.y < cosDamp && up.y > cosMax) {
Ogre::Vector3 axis = up.crossProduct(
Ogre::Vector3::UNIT_Y);
float sinTilt = axis.length();
axis /= sinTilt;
Ogre::Vector3 angVel =
m_physics->getAngularVelocity(
rigidBody.bodyID);
float mass =
std::max(1.0f, rigidBody.mass);
/* Damping along the righting axis
* (calms rocking in both directions). */
Ogre::Vector3 torque =
axis * (-1.0f * mass *
axis.dotProduct(angVel));
/* Righting torque proportional to
* sin(tilt), only past ~35 deg. */
if (up.y < cosMin)
torque += axis * (0.6f * mass *
9.81f * sinTilt);
m_physics->addTorque(rigidBody.bodyID,
torque);
}
}
updateFork(entity, vehicle, rigidBody, deltaTime);
});
}
@@ -409,6 +455,15 @@ void VehicleSystem::postPhysicsUpdate()
Ogre::Quaternion invChassis =
transform.node->_getDerivedOrientation().Inverse();
/* Jolt reports every wheel with the same axes (mirrored
* position, same axle direction), so a single wheel mesh has
* its outer face pointing inward on the right side (chassis
* -X). Flip right-side wheels 180 degrees about the local
* forward axis: the outer face points outward again and the
* spin about the axle is visually unchanged, so one shared
* wheel mesh works for all four corners. */
const Ogre::Quaternion rightSideFlip(
Ogre::Degree(180.0f), Ogre::Vector3::UNIT_Z);
for (size_t i = 0; i < it->second.nodes.size(); i++) {
Ogre::Vector3 pos;
Ogre::Quaternion ori;
@@ -417,7 +472,12 @@ void VehicleSystem::postPhysicsUpdate()
Ogre::SceneNode *node = it->second.nodes[i];
node->setPosition(transform.node->convertWorldToLocalPosition(
pos));
node->setOrientation(invChassis * ori);
bool rightSide =
i < vehicle.wheels.size() &&
vehicle.wheels[i].position.x < 0.0f;
node->setOrientation(invChassis * ori *
(rightSide ? rightSideFlip :
Ogre::Quaternion::IDENTITY));
}
/* Fork visual: follow the fork height. */
@@ -17,9 +17,79 @@ bool VehicleEditor::renderComponent(flecs::entity entity,
&vehicle.maxPitchRollAngleDeg, 0.5f,
0.0f, 90.0f))
modified = true;
if (ImGui::DragFloat("Tire Friction (long.)",
&vehicle.tireFrictionLongitudinal,
0.05f, 0.0f, 5.0f))
modified = true;
if (ImGui::DragFloat("Tire Friction (lat.)",
&vehicle.tireFrictionLateral, 0.05f,
0.0f, 5.0f))
modified = true;
if (ImGui::DragFloat("Anti-Roll Bar (N/m)",
&vehicle.antiRollBarStiffness, 50.0f,
0.0f, 20000.0f))
modified = true;
if (ImGui::DragFloat3("Seat Offset",
&vehicle.seatOffset.x, 0.01f))
modified = true;
ImGui::SameLine();
ImGui::TextDisabled("(?)");
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Legacy single driver seat; only "
"used when the Seats list below is "
"empty.");
ImGui::Text("Seats (%d)", (int)vehicle.seats.size());
ImGui::SameLine();
if (ImGui::SmallButton("Add##seat")) {
vehicle.seats.push_back(VehicleSeat());
modified = true;
}
int removeSeat = -1;
for (size_t i = 0; i < vehicle.seats.size(); i++) {
VehicleSeat &s = vehicle.seats[i];
ImGui::PushID((int)i + 1000);
Ogre::String label = s.name.empty() ?
"Seat " + std::to_string(i) :
s.name;
if (s.driver)
label += " (driver)";
if (ImGui::TreeNode(label.c_str())) {
char seatNameBuf[256];
snprintf(seatNameBuf, sizeof(seatNameBuf), "%s",
s.name.c_str());
if (ImGui::InputText("Name", seatNameBuf,
sizeof(seatNameBuf))) {
s.name = seatNameBuf;
modified = true;
}
if (ImGui::DragFloat3("Offset", &s.offset.x,
0.01f))
modified = true;
if (ImGui::Checkbox("Driver", &s.driver))
modified = true;
if (ImGui::SmallButton("Remove##seat"))
removeSeat = (int)i;
ImGui::TreePop();
}
ImGui::PopID();
}
if (removeSeat >= 0) {
vehicle.seats.erase(vehicle.seats.begin() + removeSeat);
modified = true;
}
if (ImGui::DragFloat("Seated Char Scale",
&vehicle.seatCharacterScale, 0.01f,
0.5f, 1.5f))
modified = true;
ImGui::SameLine();
ImGui::TextDisabled("(?)");
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Uniform scale applied to the "
"character while seated (restored on "
"exit); < 1 shrinks the sit pose to fit "
"the cabin.");
if (ImGui::DragFloat("Camera Distance",
&vehicle.cameraDistance, 0.05f,
1.0f, 50.0f))
+5 -5
View File
@@ -288,7 +288,7 @@ struct GUIListener : public Ogre::RenderTargetListener {
ImGuiWindowFlags_NoInputs);
// if (ECS::get().get<GUI>().enabled)
// ECS::get().get<App>().app->setWindowGrab(true);
ImGui::PushFont(bigFont);
ImGui::PushFont(bigFont, 0.0f);
ImGui::TextWrapped(
"%s",
"This game does not autosave. Please use save function to keep your state");
@@ -307,7 +307,7 @@ struct GUIListener : public Ogre::RenderTargetListener {
ImGuiCond_Always);
ImGui::Begin("Narration...", NULL,
ImGuiWindowFlags_NoTitleBar);
ImGui::PushFont(midFont);
ImGui::PushFont(midFont, 0.0f);
ImVec2 p = ImGui::GetCursorScreenPos();
GUI::NarrationHandler *narration =
ECS::get<GUI>().narrationHandlers.front();
@@ -356,7 +356,7 @@ struct GUIListener : public Ogre::RenderTargetListener {
ImGuiCond_Always);
ImGui::Begin("Narration...", NULL,
ImGuiWindowFlags_NoTitleBar);
ImGui::PushFont(midFont);
ImGui::PushFont(midFont, 0.0f);
ImVec2 p = ImGui::GetCursorScreenPos();
ImGui::TextWrapped(
"%s",
@@ -454,8 +454,8 @@ struct GUIListener : public Ogre::RenderTargetListener {
ImVec2 size = ImGui::GetWindowSize();
float window_width = size.x;
float window_height = size.y;
ImGui::PushFont(bigFont);
ImGui::TextWrapped("%s", "Booo!!!!");
ImGui::PushFont(bigFont, 0.0f);
ImGui::TextWrapped("%s", "Booo!!!!");
float extra_pixels = 20.0f;
float button_width = 0.0f;
float buttons_height = 0.0f;
+3 -2
View File
@@ -283,8 +283,9 @@ void drawRectPreview(nlohmann::json::json_pointer ptr,
// Outline if selected
if (rectName == name)
draw_list->AddRect(p_min, p_max,
IM_COL32(255, 255, 0, 255), 0.0f, 0,
2.0f);
IM_COL32(255, 255, 0, 255), 0.0f,
2.0f,
ImDrawFlags_None);
}
ImGui::Dummy(canvas_sz); // Advance cursor
}