Visual barriers for doors are now black
This commit is contained in:
@@ -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()` /
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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 "
|
||||
|
||||
+56
-56
@@ -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 "
|
||||
|
||||
@@ -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 ¢er,
|
||||
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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user