Visual barriers for doors are now black

This commit is contained in:
2026-09-11 16:38:12 +03:00
parent 867a15d6aa
commit 1159bb5a47
11 changed files with 401 additions and 129 deletions
+25 -12
View File
@@ -633,14 +633,25 @@ sceneSwitchTarget})` and emits the `door_scene_switch` event (params:
`path`, `target`, `door_id`) only when the leaf reaches `openAngle` — the
existing loading cover hides the transition. Pressing E on an
already-open scene-switch door switches immediately; a close cancels a
pending switch. The prompt stays "E Open" (never "E Close"). The door
builder also adds an unlit black occluder box (`CellGridDoorOccluderBox`
mesh + `CellGridDoorOccluderBlack` material) covering the doorway a few
cm behind the closed leaf plane, childed to the grid node (it does NOT
swing with the hinge) and tracked via `DoorComponent::occluder`;
`DoorSystem` hides it while the door is fully closed so the player never
sees the missing room interior through the opened doorway before the
switch fires.
pending switch. The prompt stays "E Open" (never "E Close"). Unlike normal
persistent doors, scene-switch doors never restore the persisted open
state: `DoorSystem::declareDoorDefaults(..., sceneSwitchDoor = true)`
resets their stored `isOpen`, so a door the player left through is always
closed again when the scene reloads (before the loading cover lifts). The
door builder also adds a black occluder covering the doorway, childed to
the grid node (it does NOT swing with the hinge) and tracked via
`DoorComponent::occluder`; `DoorSystem` hides it while the door is fully
closed so the player never sees the missing room interior through the
opened doorway before the switch fires. Doors swinging TOWARDS the player
get a flat box (`CellGridDoorOccluderBox` mesh) a few cm behind the closed
leaf plane; doors swinging AWAY (`doorSwingReversed`) get an open-front
tunnel mesh (`CellGridDoorOccluderTunnel_*`, shared per size) whose back
panel sits behind the fully-open leaf sweep, with side/top/bottom walls
flared 1 cm into the frame so no sliver of the void shows around the
frame. The shared `CellGridDoorOccluderBlack` material keeps lighting
ENABLED with all-black colours (diffuse/ambient/specular/emissive) —
with lighting disabled RTSS ignores material colours and renders the pass
white.
New `CellGridComponent` fields (serialized in the scene JSON and exposed to
Lua): `doorsEnabled`, `doorRectName`, `doorMeshName`, `doorUseMeshMaterial`,
@@ -745,10 +756,12 @@ rebuilds, scene switches and save/load (the save file's `globalState`
section). Ephemeral doors (empty `doorId`) never touch the store.
- **Defaults & restore**: `DoorBuilder::build()` calls
`DoorSystem::declareDoorDefaults(doorId, lockedByDefault)` (declares the
store defaults, returns the persisted open state) and snaps a door that
was left open straight to `openAngle` with its collider disabled — no
swing animation on load.
`DoorSystem::declareDoorDefaults(doorId, lockedByDefault,
sceneSwitchDoor)` (declares the store defaults, returns the persisted
open state) and snaps a door that was left open straight to `openAngle`
with its collider disabled — no swing animation on load. Scene-switch
doors (`sceneSwitchDoor = true`) always reopen closed instead and their
stored `isOpen` is reset, so a door left through is never ajar on return.
- **Write path**: `DoorSystem::update()` stores `isOpen` when a swing
completes.
- **Locked state**: `DoorSystem::isDoorLocked()` / `isDoorLockedById()` /
+29 -11
View File
@@ -604,7 +604,10 @@ editor-validated-unique `doorId` field on standalone doors (F2). F6 consumes
isOpen)` when a swing **completes** (reaches 0 or `openAngle`).
- Door creation (`buildDoorEntities`, standalone builder) reads the store
and snaps `currentAngle`/`isOpen`/collider to the persisted state
instantly (no swing animation on load).
instantly (no swing animation on load) — except scene-switch doors,
which always reopen closed (`declareDoorDefaults(...,
sceneSwitchDoor = true)` resets their stored `isOpen`), so a door left
through is never ajar when the player returns.
- Survives scene switches: the store is a singleton and scene switch does
not touch it; a door re-created in a revisited scene restores its state.
@@ -768,11 +771,22 @@ switch via the toggle path). `DoorSystem` fires the switch — and the new
the leaf reaches `openAngle` (`setEditorApp()` wires the `EditorApp` call;
without it only the event fires, which is what the headless test uses).
The door builder creates the black occluder (shared unit-box mesh
`CellGridDoorOccluderBox` + unlit `CellGridDoorOccluderBlack` material),
`CellGridDoorOccluderBox` + `CellGridDoorOccluderBlack` material),
childed to the grid node so it does not swing, sized from the closed-leaf
collider extents and pushed 2 cm behind the leaf plane; `DoorSystem`
hides it while fully closed. Tests: `cellgrid_door_test` test 10 (event
timing — nothing mid-swing, exactly once at full opening, close cancels).
hides it while fully closed. Doors swinging AWAY from the player
(`doorSwingReversed`) instead get an open-front tunnel mesh
(`CellGridDoorOccluderTunnel_*`, shared per doorway size): the back panel
sits behind the fully-open leaf sweep so the swinging leaf stays visible,
and side/top/bottom walls flared 1 cm into the frame keep the darkness
hole-free. The occluder material keeps lighting enabled with all-black
colours — an unlit pass renders white under RTSS (FFPColour defaults the
output to white when no lighting SRS runs). Scene-switch doors never
restore the persisted open state: `declareDoorDefaults()` resets their
stored `isOpen`, so they are always closed again when the scene reloads
(before the loading cover lifts). Tests: `cellgrid_door_test` test 10
(event timing — nothing mid-swing, exactly once at full opening, close
cancels) plus the `declareDoorDefaults` scene-switch reset in test 8.
Demo: `demo-scene-switching-extra` scene A exit doorway `Z:0:0:15` is a
scene-switch door to scene B; `--headless --test-switch` does the A→B
leg through it and asserts the switch queues only at full opening (the
@@ -794,13 +808,17 @@ sub-millisecond while swings are real-time).
`EditorApp::switchScene(sceneSwitchPath, {targetEntityName:
sceneSwitchTarget})`. The existing loading cover hides the switch.
- **Black occluder:** `buildDoorEntities()` (and the F2 standalone builder)
additionally creates an unlit black box ("cube") entity childed to the
door entity, sized to the doorway opening, placed a few cm behind the
closed leaf plane, for doors with a non-empty `sceneSwitchPath`. Its
visibility follows the door state: hidden while fully closed, visible
while open/swinging (`RenderQueue`/visible flag toggled by `DoorSystem`
from `currentAngle`), so the player never sees the missing room interior
behind the opened doorway before the switch fires.
additionally creates a black occluder entity childed to the door entity,
for doors with a non-empty `sceneSwitchPath`. Doors swinging towards the
player get a flat box sized to the doorway opening, placed a few cm
behind the closed leaf plane; doors swinging away (`swingReversed`) get
an open-front tunnel whose back panel sits behind the fully-open leaf
sweep, with side/top/bottom walls closing the gaps around the frame.
Its visibility follows the door state: hidden while fully closed, visible
while open/swinging (visible flag toggled by `DoorSystem` from
`currentAngle`), so the player never sees the missing room interior
behind the opened doorway before the switch fires. The material is lit
with all-black colours (unlit passes render white under RTSS).
- The prompt stays "E Open" (never "E Close") for scene-switch doors, as
today.
+5 -3
View File
@@ -70,10 +70,12 @@ struct DoorComponent {
// reaches openAngle. Runtime only, set by ActuatorSystem.
bool sceneSwitchPending = false;
// F1: unlit black box covering the doorway of a scene-switch door,
// F1: black box covering the doorway of a scene-switch door,
// created by the door builder a few cm behind the closed leaf plane
// (child of the grid node, so it does NOT swing with the hinge).
// Hidden while the door is fully closed. Runtime only.
// (an open-front tunnel behind the fully-open leaf sweep for doors
// swinging away from the player); child of the grid node, so it
// does NOT swing with the hinge. Hidden while the door is fully
// closed. Runtime only.
Ogre::Entity *occluder = nullptr;
};
@@ -482,6 +482,21 @@ struct SceneSwitchTestListener : public Ogre::FrameListener {
}
break;
}
/* The exit door swings TOWARDS the player, so it
* keeps the flat occluder box. */
{
const DoorComponent &d =
findDoorById(exitDoorId)
.get<DoorComponent>();
if (!d.occluder ||
d.occluder->getMesh()->getName() !=
"CellGridDoorOccluderBox") {
failed = true;
failReason =
"F1 swing-towards door lost the flat occluder";
break;
}
}
if (!requestDoorSwitch(exitDoorId))
break;
phase = 13;
@@ -608,6 +623,28 @@ struct SceneSwitchTestListener : public Ogre::FrameListener {
"after scene switch"
<< std::endl;
}
/* F1: the scene-switch exit door rebuilt with the
* scene must be closed again - scene-switch doors
* never restore the persisted open state. */
{
flecs::entity exitDoor =
findDoorById(exitDoorId);
if (!exitDoor.is_alive())
break;
const DoorComponent &d =
exitDoor.get<DoorComponent>();
if (d.isOpen || d.currentAngle != 0.0f ||
GlobalStateStore::getInstance().getBool(
"door." + exitDoorId + ".isOpen")) {
failed = true;
failReason =
"F1 exit door not closed after scene switch";
break;
}
std::cout << "[test] F1 exit door closed after "
"scene switch"
<< std::endl;
}
if (!failed) {
std::cout << "[test] arrived back in the interior "
"scene at arrival_a, camera faces the "
@@ -110,9 +110,9 @@
},
"transform": {
"position": {
"x": 4000.6,
"y": 9.9905,
"z": 3995.9
"x": 4000.60009765625,
"y": 9.990500450134277,
"z": 3995.89990234375
},
"rotation": {
"w": 0.0,
@@ -217,7 +217,7 @@
"doorRectName": "",
"doorSceneSwitchPath": "demo_scene_interior.json",
"doorSceneSwitchTarget": "arrival_a",
"doorSwingReversed": true,
"doorSwingReversed": false,
"doorUseMeshMaterial": false,
"doorsEnabled": true,
"extDoorFrameRectName": "",
@@ -365,7 +365,7 @@
"transform": {
"position": {
"x": 4000.0,
"y": 10.0148,
"y": 10.014800071716309,
"z": 4000.0
},
"rotation": {
@@ -442,14 +442,14 @@
"spawnDistance": 100.0
},
"children": [],
"id": 495,
"id": 494,
"name": {
"name": "s1"
},
"transform": {
"position": {
"x": 4000.0,
"y": 9.9039,
"y": 9.903900146484375,
"z": 3975.0
},
"rotation": {
@@ -467,7 +467,7 @@
},
{
"children": [],
"id": 496,
"id": 495,
"name": {
"name": "player"
},
@@ -517,7 +517,7 @@
},
{
"children": [],
"id": 497,
"id": 496,
"name": {
"name": "terrain"
},
@@ -525,7 +525,7 @@
"auxMaps": [],
"baseNoise": {
"amplitude": 15.0,
"frequency": 0.0003,
"frequency": 0.0003000000142492354,
"lacunarity": 2.0,
"octaves": 3,
"persistence": 0.5,
@@ -536,7 +536,7 @@
"detailNoise": {
"amplitude": 10.0,
"enabled": false,
"frequency": 0.006,
"frequency": 0.006000000052154064,
"lacunarity": 2.0,
"octaves": 4,
"persistence": 0.5,
@@ -576,12 +576,12 @@
"roadLodDistance": 200.0,
"roadMaterialName": "RoadMaterial",
"roadMeshTemplate": "road_segment.mesh",
"roadThickness": 0.3,
"roadThickness": 0.30000001192092896,
"roadVisibilityDistance": 1000.0,
"sidewalkEnabled": false,
"sidewalkHeight": 0.15,
"sidewalkHeight": 0.15000000596046448,
"sidewalkMeshTemplate": "",
"sidewalkThickness": 0.3,
"sidewalkThickness": 0.30000001192092896,
"sidewalkWidth": 1.5
},
"streamingEnabled": true,
@@ -611,89 +611,89 @@
},
{
"children": [],
"id": 498,
"id": 497,
"name": {
"name": "sky"
},
"skybox": {
"cloudiness": 0.0,
"dayBottomColor": [
0.6,
0.8,
0.6000000238418579,
0.800000011920929,
1.0
],
"dayTopColor": [
0.2,
0.20000000298023224,
0.5,
1.0
],
"enabled": true,
"moonSize": 0.03,
"moonSize": 0.029999999329447746,
"nightBottomColor": [
0.05,
0.05,
0.15
0.05000000074505806,
0.05000000074505806,
0.15000000596046448
],
"nightTopColor": [
0.0,
0.0,
0.05
0.05000000074505806
],
"size": 443.0,
"starsEnabled": false,
"sunSize": 0.05,
"sunSize": 0.05000000074505806,
"sunriseColor": [
1.0,
0.5,
0.2
0.20000000298023224
],
"sunsetColor": [
1.0,
0.3,
0.1
0.30000001192092896,
0.10000000149011612
]
},
"sun": {
"ambientDay": [
0.3,
0.3,
0.3
0.30000001192092896,
0.30000001192092896,
0.30000001192092896
],
"ambientNight": [
0.05,
0.05,
0.15
0.05000000074505806,
0.05000000074505806,
0.15000000596046448
],
"ambientSunrise": [
0.3,
0.2,
0.15
0.30000001192092896,
0.20000000298023224,
0.15000000596046448
],
"ambientSunset": [
0.25,
0.15,
0.1
0.15000000596046448,
0.10000000149011612
],
"castShadows": true,
"enabled": true,
"intensity": 1.79,
"intensity": 1.7899999618530273,
"moonColor": [
0.3,
0.3,
0.30000001192092896,
0.30000001192092896,
0.5
],
"moonSphereSize": 3.4,
"moonSphereSize": 3.4000000953674316,
"orbitTilt": 15.0,
"showMoonSphere": true,
"showSunSphere": true,
"sunColor": [
1.0,
0.95,
0.8
0.949999988079071,
0.800000011920929
],
"sunSphereSize": 5.0,
"timeOfDay": 6.226653575897217,
"timeSpeed": 0.13
"timeOfDay": 8.35942554473877,
"timeSpeed": 0.12999999523162842
},
"transform": {
"position": {
@@ -716,7 +716,7 @@
},
{
"children": [],
"id": 499,
"id": 498,
"name": {
"name": "water"
},
@@ -739,12 +739,12 @@
}
},
"waterPhysics": {
"defaultAngularDrag": 0.05,
"defaultAngularDrag": 0.05000000074505806,
"defaultBuoyancy": 1.0,
"defaultLinearDrag": 0.25,
"defaultSubmergedThreshold": 0.1,
"defaultSubmergedThreshold": 0.10000000149011612,
"enabled": true,
"gravity": 9.81,
"gravity": 9.8100004196167,
"waterDensity": 1000.0,
"waterSurfaceY": 6.0
},
@@ -752,18 +752,18 @@
"autoUpdateFromWaterPhysics": true,
"enabled": true,
"planeSize": 12000.0,
"reflectivity": 0.38,
"reflectivity": 0.3799999952316284,
"renderTextureSize": 512,
"tiling": 0.012,
"tiling": 0.012000000104308128,
"waterColor": [
0.0,
0.3,
0.30000001192092896,
0.5,
0.8
0.800000011920929
],
"waterSurfaceY": 6.0,
"waveScale": 0.031,
"waveSpeed": 0.98
"waveScale": 0.03099999949336052,
"waveSpeed": 0.9800000190734863
}
}
],
@@ -374,6 +374,21 @@ struct SceneSwitchTestListener : public Ogre::FrameListener {
}
break;
}
/* The exit door swings TOWARDS the player, so it
* keeps the flat occluder box. */
{
const DoorComponent &d =
findDoorById(exitDoorId)
.get<DoorComponent>();
if (!d.occluder ||
d.occluder->getMesh()->getName() !=
"CellGridDoorOccluderBox") {
failed = true;
failReason =
"F1 swing-towards door lost the flat occluder";
break;
}
}
if (!requestDoorSwitch(exitDoorId))
break;
phase = 13;
@@ -390,6 +405,31 @@ struct SceneSwitchTestListener : public Ogre::FrameListener {
case 1:
if (!entityExists("x1"))
break;
/* F1: the return door swings AWAY from the player
* (doorSwingReversed in demo_scene_b.json), so it
* must have the deep tunnel occluder that keeps
* the swinging leaf visible. */
{
flecs::entity returnDoor =
findDoorById(returnDoorId);
if (returnDoor.is_alive()) {
const DoorComponent &d =
returnDoor.get<DoorComponent>();
if (!d.occluder ||
d.occluder->getMesh()
->getName()
.find("CellGridDoorOccluderTunnel") !=
0) {
failed = true;
failReason =
"F1 swing-away door has no tunnel occluder";
break;
}
std::cout << "[test] F1 swing-away door "
"has tunnel occluder"
<< std::endl;
}
}
if (!checkPlayerNear(Ogre::Vector3(0.0f, 0.0f, 24.0f),
1.0f))
break;
@@ -447,6 +487,28 @@ struct SceneSwitchTestListener : public Ogre::FrameListener {
"after scene switch"
<< std::endl;
}
/* F1: the scene-switch exit door rebuilt with the
* scene must be closed again - scene-switch doors
* never restore the persisted open state. */
{
flecs::entity exitDoor =
findDoorById(exitDoorId);
if (!exitDoor.is_alive())
break;
const DoorComponent &d =
exitDoor.get<DoorComponent>();
if (d.isOpen || d.currentAngle != 0.0f ||
GlobalStateStore::getInstance().getBool(
"door." + exitDoorId + ".isOpen")) {
failed = true;
failReason =
"F1 exit door not closed after scene switch";
break;
}
std::cout << "[test] F1 exit door closed after "
"scene switch"
<< std::endl;
}
if (!failed) {
std::cout << "[test] arrived back in scene A "
"at arrival_a, camera faces the "
+152 -36
View File
@@ -14,8 +14,122 @@
#include <OgreMaterialManager.h>
#include <OgreTechnique.h>
#include <OgrePass.h>
#include <OgreManualObject.h>
#include <OgreLogManager.h>
#include <ProceduralBoxGenerator.h>
#include <cmath>
#include <cstdio>
/* F1: shared occluder resources: the flat unit box and the black
* material. NOTE: the material must keep lighting ENABLED with
* all-black colours - with lighting disabled RTSS ignores the material
* colours (FFPColour defaults the pass output to white), which made the
* occluder render white. */
static void ensureOccluderResources(const Ogre::String &group)
{
if (Ogre::MeshManager::getSingleton()
.getByName("CellGridDoorOccluderBox", group)
.isNull()) {
Procedural::BoxGenerator boxGen;
boxGen.setSizeX(1.0f).setSizeY(1.0f).setSizeZ(1.0f);
boxGen.realizeMesh("CellGridDoorOccluderBox", group);
}
if (Ogre::MaterialManager::getSingleton()
.getByName("CellGridDoorOccluderBlack", group)
.isNull()) {
Ogre::MaterialPtr mat =
Ogre::MaterialManager::getSingleton().create(
"CellGridDoorOccluderBlack", group);
Ogre::Pass *pass = mat->getTechnique(0)->getPass(0);
pass->setDiffuse(Ogre::ColourValue::Black);
pass->setAmbient(Ogre::ColourValue::Black);
pass->setSpecular(Ogre::ColourValue::Black);
pass->setEmissive(Ogre::ColourValue::Black);
pass->setCullingMode(Ogre::CULL_NONE);
}
}
/* F1: open-front black tunnel for scene-switch doors that swing AWAY
* from the player (swingReversed): the back panel sits behind the
* fully-open leaf sweep so the swinging leaf stays visible, and
* side/top/bottom walls close the gaps around the frame so no sliver of
* the ungenerated void shows. The coordinates are baked into the mesh
* in hinge-local space (+X along the doorway from the hinge, +Z away
* from the player), so the occluder node gets no position offset and no
* scale. Meshes are shared between doors of equal size. */
static Ogre::String
occluderTunnelMesh(const Ogre::Vector3 &center,
const Ogre::Vector3 &halfExtents,
const Ogre::String &group)
{
const float hx = halfExtents.x;
const float hy = halfExtents.y;
const float hz = halfExtents.z;
/* The leaf spans [0, x1] from the hinge; its tip sweeps at most
* x1 behind the closed plane (openAngle >= 90). Keep the back
* panel one leaf thickness plus a small margin behind that. */
const float x0 = center.x - hx, x1 = center.x + hx;
const float y0 = center.y - hy, y1 = center.y + hy;
const float z0 = center.z + hz + 0.02f;
const float z1 = z0 + x1 + hz * 2.0f + 0.05f;
/* Flare the walls 1 cm INTO the frame/jamb: no gap at the
* opening edges and no z-fighting with the jamb faces. */
const float f = 0.01f;
char name[160];
snprintf(name, sizeof(name),
"CellGridDoorOccluderTunnel_%d_%d_%d_%d_%d_%d",
(int)lroundf(center.x * 1000.0f),
(int)lroundf(center.y * 1000.0f),
(int)lroundf(center.z * 1000.0f),
(int)lroundf(hx * 1000.0f), (int)lroundf(hy * 1000.0f),
(int)lroundf(hz * 1000.0f));
if (!Ogre::MeshManager::getSingleton().getByName(name, group).isNull())
return name;
const float x0f = x0 - f, x1f = x1 + f;
const float y0f = y0 - f, y1f = y1 + f;
Ogre::ManualObject mo(name);
mo.begin("CellGridDoorOccluderBlack",
Ogre::RenderOperation::OT_TRIANGLE_LIST);
auto quad = [&mo](const Ogre::Vector3 &a, const Ogre::Vector3 &b,
const Ogre::Vector3 &c, const Ogre::Vector3 &d,
const Ogre::Vector3 &normal) {
Ogre::uint32 base = mo.getCurrentVertexCount();
mo.position(a);
mo.normal(normal);
mo.position(b);
mo.normal(normal);
mo.position(c);
mo.normal(normal);
mo.position(d);
mo.normal(normal);
mo.triangle(base, base + 1, base + 2);
mo.triangle(base, base + 2, base + 3);
};
/* Back panel (facing the player, -Z). */
quad(Ogre::Vector3(x0f, y0f, z1), Ogre::Vector3(x1f, y0f, z1),
Ogre::Vector3(x1f, y1f, z1), Ogre::Vector3(x0f, y1f, z1),
Ogre::Vector3::NEGATIVE_UNIT_Z);
/* Left / right walls (facing into the tunnel). */
quad(Ogre::Vector3(x0f, y0f, z0), Ogre::Vector3(x0f, y1f, z0),
Ogre::Vector3(x0f, y1f, z1), Ogre::Vector3(x0f, y0f, z1),
Ogre::Vector3::UNIT_X);
quad(Ogre::Vector3(x1f, y0f, z0), Ogre::Vector3(x1f, y0f, z1),
Ogre::Vector3(x1f, y1f, z1), Ogre::Vector3(x1f, y1f, z0),
Ogre::Vector3::NEGATIVE_UNIT_X);
/* Bottom / top walls. */
quad(Ogre::Vector3(x0f, y0f, z0), Ogre::Vector3(x1f, y0f, z0),
Ogre::Vector3(x1f, y0f, z1), Ogre::Vector3(x0f, y0f, z1),
Ogre::Vector3::UNIT_Y);
quad(Ogre::Vector3(x0f, y1f, z0), Ogre::Vector3(x0f, y1f, z1),
Ogre::Vector3(x1f, y1f, z1), Ogre::Vector3(x1f, y1f, z0),
Ogre::Vector3::NEGATIVE_UNIT_Y);
mo.end();
mo.convertToMesh(name, group);
return name;
}
flecs::entity DoorBuilder::build(flecs::world &world,
Ogre::SceneManager *sceneMgr,
@@ -121,11 +235,15 @@ flecs::entity DoorBuilder::build(flecs::world &world,
/* F6: declare the store defaults and snap the door to the
* persisted open state (rebuilds and reloads restore the
* state the door had when it was saved/left). */
* state the door had when it was saved/left). Scene-switch
* doors are the exception: they always reopen closed (the
* switch fires from the fully-open pose, so a persisted
* "open" would restore the door ajar on return). */
bool wasOpen = false;
if (!door.doorId.empty()) {
wasOpen = DoorSystem::declareDoorDefaults(
door.doorId, params.lockedByDefault);
door.doorId, params.lockedByDefault,
!door.sceneSwitchPath.empty());
}
if (wasOpen) {
door.isOpen = true;
@@ -164,37 +282,45 @@ flecs::entity DoorBuilder::build(flecs::world &world,
collider.offset = colliderCenter;
colliderEntity.set<PhysicsColliderComponent>(collider);
/* F1: scene-switch doors get an unlit black occluder box
* covering the doorway, a few cm behind the closed leaf
* plane, so the player never sees the missing room
* interior through the opened doorway before the switch
* fires. The node hangs off the PARENT node (not the
* hinge), so it does not swing with the leaf; DoorSystem
/* F1: scene-switch doors get a black occluder covering the
* doorway, so the player never sees the missing room interior
* through the opened doorway before the switch fires. Doors
* swinging TOWARDS the player keep the flat box a few cm behind
* the closed leaf plane; doors swinging AWAY (swingReversed) get
* an open-front tunnel whose back panel sits behind the
* fully-open leaf sweep, with side/top/bottom walls closing the
* gaps around the frame. The node hangs off the PARENT node
* (not the hinge), so it does not swing with the leaf; DoorSystem
* toggles its visibility from the swing angle. */
if (!door.sceneSwitchPath.empty()) {
const Ogre::String group =
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME;
if (Ogre::MeshManager::getSingleton()
.getByName("CellGridDoorOccluderBox", group)
.isNull()) {
Procedural::BoxGenerator boxGen;
boxGen.setSizeX(1.0f).setSizeY(1.0f).setSizeZ(1.0f);
boxGen.realizeMesh("CellGridDoorOccluderBox", group);
}
if (Ogre::MaterialManager::getSingleton()
.getByName("CellGridDoorOccluderBlack", group)
.isNull()) {
Ogre::MaterialPtr mat =
Ogre::MaterialManager::getSingleton().create(
"CellGridDoorOccluderBlack", group);
Ogre::Pass *pass = mat->getTechnique(0)->getPass(0);
pass->setLightingEnabled(false);
pass->setDiffuse(Ogre::ColourValue::Black);
ensureOccluderResources(group);
Ogre::String meshName = "CellGridDoorOccluderBox";
Ogre::Vector3 occScale(colliderHalfExtents.x * 2.0f,
colliderHalfExtents.y * 2.0f,
0.05f);
Ogre::Vector3 occPos =
hingePos +
doorwayRot *
(colliderCenter +
Ogre::Vector3(0, 0,
colliderHalfExtents.z +
0.02f));
if (params.swingReversed) {
/* Swing-away door: deep tunnel occluder with the
* geometry baked into the mesh (hinge-local). */
meshName = occluderTunnelMesh(colliderCenter,
colliderHalfExtents,
group);
occScale = Ogre::Vector3::UNIT_SCALE;
occPos = hingePos;
}
Ogre::Entity *occEnt = nullptr;
try {
occEnt = sceneMgr->createEntity("CellGridDoorOccluderBox");
occEnt = sceneMgr->createEntity(meshName);
} catch (const std::exception &e) {
Ogre::LogManager::getSingleton().logMessage(
"DoorBuilder: Error creating door occluder: " +
@@ -204,16 +330,6 @@ flecs::entity DoorBuilder::build(flecs::world &world,
occEnt->setMaterialName("CellGridDoorOccluderBlack");
occEnt->setVisible(wasOpen);
Ogre::Vector3 occScale(colliderHalfExtents.x * 2.0f,
colliderHalfExtents.y * 2.0f,
0.05f);
Ogre::Vector3 occPos =
hingePos +
doorwayRot *
(colliderCenter +
Ogre::Vector3(0, 0,
colliderHalfExtents.z +
0.02f));
Ogre::SceneNode *occNode =
parentNode->createChildSceneNode();
occNode->setPosition(occPos);
@@ -227,7 +343,7 @@ flecs::entity DoorBuilder::build(flecs::world &world,
{occNode, occPos, doorwayRot, occScale});
RenderableComponent occRenderable;
occRenderable.entity = occEnt;
occRenderable.meshName = "CellGridDoorOccluderBox";
occRenderable.meshName = meshName;
occEntity.set<RenderableComponent>(occRenderable);
doorEntity.get_mut<DoorComponent>().occluder = occEnt;
@@ -18,13 +18,16 @@
* leaf, DoorComponent, RigidBodyComponent static, ActuatorComponent)
* collider child (PhysicsColliderComponent box of the closed
* leaf; disabled by DoorSystem while not fully closed)
* occluder child (F1, scene-switch doors only: unlit black box
* behind the leaf plane, node on the PARENT node so it does not
* swing; DoorSystem toggles its visibility)
* occluder child (F1, scene-switch doors only: black box
* behind the leaf plane - an open-front tunnel for doors
* swinging away from the player -, node on the PARENT node so
* it does not swing; DoorSystem toggles its visibility)
*
* The builder also declares the F6 store defaults for persistent doors
* (DoorSystem::declareDoorDefaults) and snaps a door that was left open
* straight to its persisted pose with the collider disabled.
* straight to its persisted pose with the collider disabled; scene-switch
* doors always reopen closed instead (declareDoorDefaults resets their
* stored isOpen).
*
* Leaf mesh creation stays with the caller: pass a ready mesh name
* (CellGrid generates its UV-mapped procedural leaf; standalone doors
+10 -2
View File
@@ -67,7 +67,7 @@ void DoorSystem::setDoorLocked(const std::string &doorId, bool locked)
}
bool DoorSystem::declareDoorDefaults(const std::string &doorId,
bool lockedByDefault)
bool lockedByDefault, bool sceneSwitchDoor)
{
if (doorId.empty())
return false;
@@ -75,7 +75,15 @@ bool DoorSystem::declareDoorDefaults(const std::string &doorId,
GlobalStateStore &store = GlobalStateStore::getInstance();
store.declareDefault("door." + doorId + ".locked", lockedByDefault);
store.declareDefault("door." + doorId + ".isOpen", false);
return store.getBool("door." + doorId + ".isOpen");
bool isOpen = store.getBool("door." + doorId + ".isOpen");
if (isOpen && sceneSwitchDoor) {
/* Scene-switch doors always reopen closed (the switch
* fires from the fully-open pose); reset the stored state
* so Lua/queries never see a stale "open". */
store.set("door." + doorId + ".isOpen", false);
isOpen = false;
}
return isOpen;
}
Ogre::Quaternion DoorSystem::swingOrientation(const DoorComponent &door,
+11 -5
View File
@@ -20,8 +20,9 @@
* Persistent door state (F6) lives in the GlobalStateStore under
* "door.<doorId>.locked" / "door.<doorId>.isOpen" (doorId is the F0 global
* door ID "<gridUid>:<edgeKey>"; empty = ephemeral door). The system
* writes isOpen when a swing completes; CellGridSystem snaps a rebuilt
* door to the persisted state. Locked state is managed through the
* writes isOpen when a swing completes; the door builders snap a rebuilt
* door to the persisted state (except scene-switch doors, which always
* reopen closed). Locked state is managed through the
* static helpers below and through the EventBus: sending
* "door_unlock_<doorId>" or the generic "door_unlock" event (param
* door_id) unlocks a door; "door_unlocked_<doorId>" / "door_unlocked" are
@@ -45,10 +46,15 @@ public:
static void setDoorLocked(const std::string &doorId, bool locked);
/* Declare the store defaults for a persistent door (called by the
* door builders when a door is (re)created) and return the
* persisted open state. */
* door builders when a door is (re)created) and return the open
* state the door should start in. Scene-switch doors
* (sceneSwitchDoor = true) always reopen closed and their stored
* isOpen is reset: the switch fires from the fully-open pose, so a
* persisted "open" would otherwise restore the door ajar on
* return. */
static bool declareDoorDefaults(const std::string &doorId,
bool lockedByDefault);
bool lockedByDefault,
bool sceneSwitchDoor = false);
/* Hinge orientation for a swing angle (F3: swingReversed negates
* the applied angle; the stored angles stay positive). Used by
@@ -462,6 +462,13 @@ static int testDoorOpenStatePersisted()
if (!DoorSystem::declareDoorDefaults(id, false))
FAIL("persisted open state not returned on rebuild");
// Scene-switch doors always reopen closed: the persisted open
// state is ignored and the store key is reset.
if (DoorSystem::declareDoorDefaults(id, false, true))
FAIL("scene-switch door restored open");
if (store.getBool(openKey))
FAIL("scene-switch door did not reset the stored open state");
PASS();
return 0;
}