Compare commits

...

2 Commits

Author SHA1 Message Date
slapin 7aa7682e97 Fixed black tunnels 2026-09-11 18:39:02 +03:00
slapin 1159bb5a47 Visual barriers for doors are now black 2026-09-11 16:38:12 +03:00
12 changed files with 416 additions and 136 deletions
+28 -12
View File
@@ -633,14 +633,28 @@ 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. Every scene-switch door gets the
same open-front tunnel mesh (`CellGridDoorOccluderTunnel_*`, shared per
size and side) on the VOID side of the doorway: door-local +Z points
outward from the owning cell, where normal grids hide the missing room,
while exteriorOnly grids hide their missing interior inward, so
`CellGridSystem` passes `DoorBuildParams::occluderSide = -1` there (the
builder default is +1; standalone doors keep it). The tunnel depth covers
the fully-open leaf sweep, so the leaf stays visible whether it swings
towards or away from the player, and the side/top/bottom walls flared
1 cm into the frame keep the darkness hole-free. 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 +759,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()` /
+37 -14
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.
@@ -767,12 +770,25 @@ switch via the toggle path). `DoorSystem` fires the switch — and the new
`door_scene_switch` event (params `path`/`target`/`door_id`) — only when
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),
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).
The door builder creates the black occluder (open-front tunnel mesh
`CellGridDoorOccluderTunnel_*`, shared per doorway size and side, +
`CellGridDoorOccluderBlack` material), childed to the grid node so it
does not swing. Every scene-switch door gets the same tunnel on the VOID
side of the doorway — door-local +Z points outward from the owning cell
(normal grids hide the missing room there); `CellGridSystem` passes
`occluderSide = -1` for exteriorOnly grids, whose missing interior is
inward. The back panel sits behind the fully-open leaf sweep so the
swinging leaf stays visible for both swing directions, and
side/top/bottom walls flared 1 cm into the frame keep the darkness
hole-free; `DoorSystem` hides the occluder while fully closed. 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 +810,20 @@ 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`. All of them get the same
open-front tunnel on the VOID side of the doorway
(`DoorBuildParams::occluderSide`: +1 = door-local +Z, outward from the
owning cell, the default; `CellGridSystem` passes -1 for exteriorOnly
grids, whose missing interior is inward). The tunnel depth covers the
fully-open leaf sweep, so the leaf stays visible whether it swings
towards or away from the player, and side/top/bottom walls close 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.
+7 -4
View File
@@ -70,10 +70,13 @@ struct DoorComponent {
// reaches openAngle. Runtime only, set by ActuatorSystem.
bool sceneSwitchPending = false;
// F1: unlit 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.
// F1: black open-front tunnel covering the doorway of a scene-switch
// door, created by the door builder on the VOID side of the doorway
// (door-local +Z outward from the owning cell; -Z for exteriorOnly
// grids); its depth covers the fully-open leaf sweep, so the leaf
// stays visible for both swing directions. 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,24 @@ struct SceneSwitchTestListener : public Ogre::FrameListener {
}
break;
}
/* F1: every scene-switch door gets the void-side
* tunnel occluder (depth covers the leaf sweep,
* so both swing directions stay visible). */
{
const DoorComponent &d =
findDoorById(exitDoorId)
.get<DoorComponent>();
if (!d.occluder ||
d.occluder->getMesh()
->getName()
.find("CellGridDoorOccluderTunnel") !=
0) {
failed = true;
failReason =
"F1 exit door has no tunnel occluder";
break;
}
}
if (!requestDoorSwitch(exitDoorId))
break;
phase = 13;
@@ -608,6 +626,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,24 @@ struct SceneSwitchTestListener : public Ogre::FrameListener {
}
break;
}
/* F1: every scene-switch door gets the void-side
* tunnel occluder (depth covers the leaf sweep,
* so both swing directions stay visible). */
{
const DoorComponent &d =
findDoorById(exitDoorId)
.get<DoorComponent>();
if (!d.occluder ||
d.occluder->getMesh()
->getName()
.find("CellGridDoorOccluderTunnel") !=
0) {
failed = true;
failReason =
"F1 exit door has no tunnel occluder";
break;
}
}
if (!requestDoorSwitch(exitDoorId))
break;
phase = 13;
@@ -390,6 +408,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); the
* void-side tunnel occluder covers the full leaf
* sweep, so the swinging leaf stays 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 +490,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 "
@@ -5065,6 +5065,11 @@ void CellGridSystem::buildDoorEntities(flecs::entity entity,
!params.sceneSwitchPath.empty())
params.doorId = grid.ensureGridUid() + ":" + key;
params.edgeKey = key;
/* F1: the occluder tunnel goes on the VOID side of the
* doorway: door-local +Z points outward from the owning
* cell, where normal grids hide the missing room; for
* exteriorOnly grids the missing interior is inward (-Z). */
params.occluderSide = grid.exteriorOnlyMode() ? -1.0f : 1.0f;
params.friction = grid.friction;
params.leafMeshName = leafMeshName;
params.customMesh = usingCustomMesh;
+135 -39
View File
@@ -14,8 +14,114 @@
#include <OgreMaterialManager.h>
#include <OgreTechnique.h>
#include <OgrePass.h>
#include <OgreManualObject.h>
#include <OgreLogManager.h>
#include <ProceduralBoxGenerator.h>
#include <cmath>
#include <cstdio>
/* F1: the black occluder 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 ensureOccluderMaterial(const Ogre::String &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, covering the
* doorway on the void side (door-local Z times `side`): the back panel
* sits behind the fully-open leaf sweep so the swinging leaf stays
* visible whichever way it swings, 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, the tunnel extending to `side` *
* Z), so the occluder node gets no position offset and no scale.
* Meshes are shared between doors of equal size and side. */
static Ogre::String
occluderTunnelMesh(const Ogre::Vector3 &center,
const Ogre::Vector3 &halfExtents, float side,
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 + side * (hz + 0.02f);
const float z1 = z0 + side * (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_%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), (int)side);
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, against the tunnel depth). */
quad(Ogre::Vector3(x0f, y0f, z1), Ogre::Vector3(x1f, y0f, z1),
Ogre::Vector3(x1f, y1f, z1), Ogre::Vector3(x0f, y1f, z1),
Ogre::Vector3(0, 0, -side));
/* 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 +227,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 +274,33 @@ 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
* toggles its visibility from the swing angle. */
/* 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. All of
* them get the same open-front tunnel on the VOID side of the
* doorway (params.occluderSide: door-local +Z outward from the
* owning cell, -Z for exteriorOnly grids whose missing interior
* is inward); its depth covers the fully-open leaf sweep, so the
* leaf stays visible whether it swings towards or away from the
* player, and the side/top/bottom walls close the gaps around
* the frame. The geometry is baked into the mesh in hinge-local
* space and 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);
}
ensureOccluderMaterial(group);
Ogre::String meshName = occluderTunnelMesh(
colliderCenter, colliderHalfExtents,
params.occluderSide, group);
Ogre::Vector3 occScale = Ogre::Vector3::UNIT_SCALE;
Ogre::Vector3 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 +310,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 +323,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;
+15 -4
View File
@@ -18,13 +18,17 @@
* 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: open-front
* black tunnel on the void side of the doorway - door-local
* +Z normally, -Z for exteriorOnly grids -, 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
@@ -37,6 +41,13 @@ struct DoorBuildParams {
float openAngle = 100.0f; // degrees
float openSpeed = 180.0f; // degrees per second
bool swingReversed = false; // F3
/* F1: side of the doorway the scene-switch occluder tunnel
* extends to, in door-local Z: +1 = +Z (outward from the owning
* cell - the default; the missing room/void is there), -1 = -Z
* (exteriorOnly grids, whose missing interior is inward). The
* tunnel depth covers the full leaf sweep, so either swing
* direction stays visible. */
float occluderSide = 1.0f;
std::string actionName; // optional actuator action
std::string sceneSwitchPath; // F1 (empty = normal swinging door)
std::string sceneSwitchTarget;
+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;
}