Fixed light bleeding throw scene switching doors

This commit is contained in:
2026-09-11 21:53:36 +03:00
parent 7aa7682e97
commit 7a388d2a6f
7 changed files with 214 additions and 19 deletions
+12 -2
View File
@@ -642,7 +642,15 @@ 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
opened doorway before the switch fires. A second flat black **gap shield**
quad (`CellGridDoorOccluderShield_*`, tracked via `DoorComponent::gapShield`)
sits 1 cm behind the closed leaf's back face on the same void side and is
visible exactly while the door is fully closed: spanning the leaf box plus
a 6 cm margin, it blacks out the leaf/frame clearance slits (0.02 m per
side, 0.05 m at the top) that would otherwise show the ungenerated void
(sky/terrain) around the closed door; its margin edges end up inside the
jamb boxes and wall thickness, so it never pokes out of the frame. 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,
@@ -651,7 +659,9 @@ while exteriorOnly grids hide their missing interior inward, so
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
6 cm into the frame — past the 0.02 m side / 0.05 m top leaf clearances,
so their edges sit inside the jamb boxes and no sight line slips between
the frame and the tunnel into the void — 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.
+17 -3
View File
@@ -779,8 +779,18 @@ side of the doorway — door-local +Z points outward from the owning cell
`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
side/top/bottom walls flared 6 cm into the frame — past the 0.02 m side /
0.05 m top leaf clearances, so their edges sit inside the jamb boxes and
no sight line slips between the frame and the tunnel into the void — keep the darkness
hole-free; `DoorSystem` hides the occluder while fully closed. A second
flat black gap-shield quad (`CellGridDoorOccluderShield_*`, tracked via
`DoorComponent::gapShield`) sits 1 cm behind the closed leaf's back face
on the same void side and is visible exactly while the door is fully
closed: spanning the leaf box plus a 6 cm margin, it blacks out the
leaf/frame clearance slits (0.02 m per side, 0.05 m at the top) that
otherwise show the ungenerated void (sky/terrain) around the closed
door; its margin edges end up inside the jamb boxes and wall thickness,
so it never pokes out of the frame. 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
@@ -822,7 +832,11 @@ sub-millisecond while swings are real-time).
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
behind the opened doorway before the switch fires. A complementary flat
gap-shield quad just behind the closed leaf (leaf box + 6 cm margin,
covering the 0.02 m side / 0.05 m top clearances) is visible exactly
while the door is fully closed, blacking out the clearance slits. 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.
@@ -78,6 +78,13 @@ struct DoorComponent {
// so it does NOT swing with the hinge. Hidden while the door is
// fully closed. Runtime only.
Ogre::Entity *occluder = nullptr;
// F1: flat black gap-shield quad just behind the closed leaf of a
// scene-switch door (same void side as the tunnel), visible only
// while the door is fully closed - it blacks out the leaf/frame
// clearance slits (0.02 m sides, 0.05 m top) the tunnel, hidden
// while closed, does not cover. Runtime only.
Ogre::Entity *gapShield = nullptr;
};
#endif // EDITSCENE_DOOR_HPP
@@ -484,7 +484,9 @@ struct SceneSwitchTestListener : public Ogre::FrameListener {
}
/* F1: every scene-switch door gets the void-side
* tunnel occluder (depth covers the leaf sweep,
* so both swing directions stay visible). */
* so both swing directions stay visible) plus the
* gap shield backing the leaf/frame clearance
* slits while the door is closed. */
{
const DoorComponent &d =
findDoorById(exitDoorId)
@@ -499,6 +501,49 @@ struct SceneSwitchTestListener : public Ogre::FrameListener {
"F1 exit door has no tunnel occluder";
break;
}
if (!d.gapShield ||
d.gapShield->getMesh()
->getName()
.find("CellGridDoorOccluderShield") !=
0) {
failed = true;
failReason =
"F1 exit door has no gap shield";
break;
}
/* The door is still closed here: the shield
* backs the slits, the tunnel stays hidden. */
if (!d.gapShield->isVisible() ||
d.occluder->isVisible()) {
failed = true;
failReason =
"F1 closed-door occluder visibility wrong";
break;
}
/* The tunnel walls must reach past the
* leaf/frame clearances (0.02 m sides,
* 0.05 m top) or the void shows between the
* frame and the black corridor while the
* door is open; both meshes are hinge-local,
* so the tunnel footprint must cover at
* least the gap shield's (6 cm margin). */
{
Ogre::Vector3 th =
d.occluder->getMesh()
->getBounds()
.getHalfSize();
Ogre::Vector3 sh =
d.gapShield->getMesh()
->getBounds()
.getHalfSize();
if (th.x < sh.x - 0.001f ||
th.y < sh.y - 0.001f) {
failed = true;
failReason =
"F1 tunnel walls inside the leaf/frame clearances";
break;
}
}
}
if (!requestDoorSwitch(exitDoorId))
break;
@@ -730,7 +775,10 @@ struct SceneSwitchTestListener : public Ogre::FrameListener {
* Z:0:0:0 is the way back. E on such a door swings the leaf open first;
* the scene switch fires only when the leaf reaches the open angle, and
* a black occluder box behind the doorway hides the missing half of the
* building while it swings. Both scenes carry their own player
* building while it swings; while the door is closed a flat black gap
* shield behind the leaf blacks out the leaf/frame clearance slits, so
* no sky/terrain bleeds through the closed door. Both scenes carry
* their own player
* controller, so each switch is a clean takeover (see
* EditorApp::performSceneSwitch); travel back and forth is endless.
*
@@ -752,7 +800,9 @@ struct SceneSwitchTestListener : public Ogre::FrameListener {
* persisted and restored across the scene switches) and
* both F1 scene-switch doors (interior -> exterior and
* back each fire only when the leaf is fully open,
* black occluder present), verifying the round-trip
* black occluder tunnel + closed-door gap shield
* present with the right closed-state visibility),
* verifying the round-trip
* arrival teleports (the exterior arrival check reads
* the expected Y from TerrainSystem::getHeightAt and
* polls while the streaming window loads and the
+111 -4
View File
@@ -64,9 +64,16 @@ occluderTunnelMesh(const Ogre::Vector3 &center,
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;
/* Flare the walls INTO the frame/jamb past the leaf/frame
* clearances (0.02 m per side, 0.05 m at the top): with a smaller
* flare, sight lines through the open doorway slip over the top
* wall / past the side walls into the void between the jamb inner
* face and the wall, showing sky/terrain around the black
* corridor. 6 cm puts the wall edges inside the jamb boxes (0.1 m
* thick, inner faces at the clearances), so any ray past a wall
* hits the frame instead, with no z-fighting against the jamb
* faces. */
const float f = 0.06f;
char name[160];
snprintf(name, sizeof(name),
@@ -123,6 +130,67 @@ occluderTunnelMesh(const Ogre::Vector3 &center,
return name;
}
/* F1: flat black gap shield for scene-switch doors, shown while the
* door is fully closed (the tunnel covers the swinging/open states):
* the leaf is sized with clearances against the frame opening (0.02 m
* per side, 0.05 m at the top), and without a backing those slits show
* the ungenerated void (sky/terrain) around the closed door. The
* shield is a single quad 1 cm behind the closed leaf's back face on
* the void side (`side` * Z), spanning the leaf box plus a margin that
* exceeds the clearances; the margin edges end up inside the jamb
* boxes and the wall thickness, so the quad never pokes out of the
* frame. Like the tunnel, the coordinates are baked into the mesh in
* hinge-local space and meshes are shared between doors of equal size
* and side. */
static Ogre::String
occluderShieldMesh(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;
const float x0 = center.x - hx, x1 = center.x + hx;
const float y0 = center.y - hy, y1 = center.y + hy;
const float z = center.z + side * (hz + 0.01f);
/* Margin beyond the leaf box: must exceed the leaf/frame
* clearances (0.02 m sides, 0.05 m top). */
const float m = 0.06f;
char name[160];
snprintf(name, sizeof(name),
"CellGridDoorOccluderShield_%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 x0m = x0 - m, x1m = x1 + m;
const float y0m = y0 - m, y1m = y1 + m;
Ogre::ManualObject mo(name);
mo.begin("CellGridDoorOccluderBlack",
Ogre::RenderOperation::OT_TRIANGLE_LIST);
Ogre::Vector3 normal(0, 0, -side);
Ogre::uint32 base = mo.getCurrentVertexCount();
mo.position(x0m, y0m, z);
mo.normal(normal);
mo.position(x1m, y0m, z);
mo.normal(normal);
mo.position(x1m, y1m, z);
mo.normal(normal);
mo.position(x0m, y1m, z);
mo.normal(normal);
mo.triangle(base, base + 1, base + 2);
mo.triangle(base, base + 2, base + 3);
mo.end();
mo.convertToMesh(name, group);
return name;
}
flecs::entity DoorBuilder::build(flecs::world &world,
Ogre::SceneManager *sceneMgr,
flecs::entity parentEntity,
@@ -286,7 +354,10 @@ flecs::entity DoorBuilder::build(flecs::world &world,
* 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. */
* visibility from the swing angle. A second flat gap-shield quad
* sits just behind the closed leaf and is visible only while the
* door is fully closed, blacking out the leaf/frame clearance
* slits the tunnel (hidden while closed) does not cover. */
if (!door.sceneSwitchPath.empty()) {
const Ogre::String group =
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME;
@@ -328,6 +399,42 @@ flecs::entity DoorBuilder::build(flecs::world &world,
doorEntity.get_mut<DoorComponent>().occluder = occEnt;
}
Ogre::String shieldMeshName = occluderShieldMesh(
colliderCenter, colliderHalfExtents,
params.occluderSide, group);
Ogre::Entity *shieldEnt = nullptr;
try {
shieldEnt = sceneMgr->createEntity(shieldMeshName);
} catch (const std::exception &e) {
Ogre::LogManager::getSingleton().logMessage(
"DoorBuilder: Error creating door gap shield: " +
std::string(e.what()));
}
if (shieldEnt) {
shieldEnt->setMaterialName("CellGridDoorOccluderBlack");
shieldEnt->setVisible(!wasOpen);
Ogre::SceneNode *shieldNode =
parentNode->createChildSceneNode();
shieldNode->setPosition(occPos);
shieldNode->setOrientation(doorwayRot);
shieldNode->setScale(occScale);
shieldNode->attachObject(shieldEnt);
flecs::entity shieldEntity = world.entity();
shieldEntity.child_of(doorEntity);
shieldEntity.set<TransformComponent>(
{shieldNode, occPos, doorwayRot, occScale});
RenderableComponent shieldRenderable;
shieldRenderable.entity = shieldEnt;
shieldRenderable.meshName = shieldMeshName;
shieldEntity.set<RenderableComponent>(
shieldRenderable);
doorEntity.get_mut<DoorComponent>().gapShield =
shieldEnt;
}
}
return doorEntity;
@@ -18,11 +18,15 @@
* 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: 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)
* 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, hidden while the door is fully closed)
* gap-shield child (F1, scene-switch doors only: flat black
* quad just behind the closed leaf, visible only while the
* door is fully closed; blacks out the leaf/frame clearance
* slits the hidden tunnel does not cover)
*
* The builder also declares the F6 store defaults for persistent doors
* (DoorSystem::declareDoorDefaults) and snaps a door that was left open
@@ -175,10 +175,13 @@ void DoorSystem::update(float deltaTime)
}
}
// F1: the black occluder is visible unless the door is
// fully closed
// F1: the black occluder tunnel is visible unless the door
// is fully closed; the gap shield backs the leaf/frame
// clearance slits exactly while it is
if (door.occluder)
door.occluder->setVisible(door.currentAngle != 0.0f);
if (door.gapShield)
door.gapShield->setVisible(door.currentAngle == 0.0f);
});
// Refresh the per-door "door_unlock_<doorId>" subscriptions