Fixed black tunnels

This commit is contained in:
2026-09-11 18:39:02 +03:00
parent 1159bb5a47
commit 7aa7682e97
8 changed files with 114 additions and 106 deletions
+13 -10
View File
@@ -642,16 +642,19 @@ 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.
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`,
+20 -15
View File
@@ -770,16 +770,18 @@ 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` + `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. 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
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
@@ -809,11 +811,14 @@ sub-millisecond while swings are real-time).
sceneSwitchTarget})`. The existing loading cover hides the switch.
- **Black occluder:** `buildDoorEntities()` (and the F2 standalone builder)
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.
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
+7 -6
View File
@@ -70,12 +70,13 @@ struct DoorComponent {
// reaches openAngle. Runtime only, set by ActuatorSystem.
bool sceneSwitchPending = false;
// F1: black box covering the doorway of a scene-switch door,
// created by the door builder a few cm behind the closed leaf plane
// (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.
// 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,18 +482,21 @@ struct SceneSwitchTestListener : public Ogre::FrameListener {
}
break;
}
/* The exit door swings TOWARDS the player, so it
* keeps the flat occluder box. */
/* 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() !=
"CellGridDoorOccluderBox") {
d.occluder->getMesh()
->getName()
.find("CellGridDoorOccluderTunnel") !=
0) {
failed = true;
failReason =
"F1 swing-towards door lost the flat occluder";
"F1 exit door has no tunnel occluder";
break;
}
}
@@ -374,18 +374,21 @@ struct SceneSwitchTestListener : public Ogre::FrameListener {
}
break;
}
/* The exit door swings TOWARDS the player, so it
* keeps the flat occluder box. */
/* 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() !=
"CellGridDoorOccluderBox") {
d.occluder->getMesh()
->getName()
.find("CellGridDoorOccluderTunnel") !=
0) {
failed = true;
failReason =
"F1 swing-towards door lost the flat occluder";
"F1 exit door has no tunnel occluder";
break;
}
}
@@ -406,9 +409,9 @@ struct SceneSwitchTestListener : public Ogre::FrameListener {
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. */
* (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);
@@ -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;
+38 -58
View File
@@ -16,24 +16,15 @@
#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)
/* 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::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()) {
@@ -49,17 +40,18 @@ static void ensureOccluderResources(const Ogre::String &group)
}
}
/* 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. */
/* 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,
const Ogre::Vector3 &halfExtents, float side,
const Ogre::String &group)
{
const float hx = halfExtents.x;
@@ -70,20 +62,20 @@ occluderTunnelMesh(const Ogre::Vector3 &center,
* 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;
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",
"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)lroundf(hz * 1000.0f), (int)side);
if (!Ogre::MeshManager::getSingleton().getByName(name, group).isNull())
return name;
@@ -108,10 +100,10 @@ occluderTunnelMesh(const Ogre::Vector3 &center,
mo.triangle(base, base + 1, base + 2);
mo.triangle(base, base + 2, base + 3);
};
/* Back panel (facing the player, -Z). */
/* 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::NEGATIVE_UNIT_Z);
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),
@@ -284,39 +276,27 @@ flecs::entity DoorBuilder::build(flecs::world &world,
/* 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. */
* 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;
ensureOccluderResources(group);
ensureOccluderMaterial(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::String meshName = occluderTunnelMesh(
colliderCenter, colliderHalfExtents,
params.occluderSide, group);
Ogre::Vector3 occScale = Ogre::Vector3::UNIT_SCALE;
Ogre::Vector3 occPos = hingePos;
Ogre::Entity *occEnt = nullptr;
try {
+12 -4
View File
@@ -18,10 +18,11 @@
* 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: 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)
* 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
@@ -40,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;