diff --git a/src/features/editScene/AGENTS.md b/src/features/editScene/AGENTS.md index 02ce1e9..a4a8e3d 100644 --- a/src/features/editScene/AGENTS.md +++ b/src/features/editScene/AGENTS.md @@ -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`, diff --git a/src/features/editScene/GameFeatures202609.md b/src/features/editScene/GameFeatures202609.md index e601c2e..caf6870 100644 --- a/src/features/editScene/GameFeatures202609.md +++ b/src/features/editScene/GameFeatures202609.md @@ -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 diff --git a/src/features/editScene/components/Door.hpp b/src/features/editScene/components/Door.hpp index 9ce9edd..fb62849 100644 --- a/src/features/editScene/components/Door.hpp +++ b/src/features/editScene/components/Door.hpp @@ -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; }; diff --git a/src/features/editScene/demos/demo-interior-exterior-dynamics/demo_main.cpp b/src/features/editScene/demos/demo-interior-exterior-dynamics/demo_main.cpp index e5f9b94..014aafc 100644 --- a/src/features/editScene/demos/demo-interior-exterior-dynamics/demo_main.cpp +++ b/src/features/editScene/demos/demo-interior-exterior-dynamics/demo_main.cpp @@ -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(); 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; } } diff --git a/src/features/editScene/demos/demo-scene-switching-extra/demo_main.cpp b/src/features/editScene/demos/demo-scene-switching-extra/demo_main.cpp index d2f75dc..0896222 100644 --- a/src/features/editScene/demos/demo-scene-switching-extra/demo_main.cpp +++ b/src/features/editScene/demos/demo-scene-switching-extra/demo_main.cpp @@ -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(); 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); diff --git a/src/features/editScene/systems/CellGridSystem.cpp b/src/features/editScene/systems/CellGridSystem.cpp index 2916ce9..9c83781 100644 --- a/src/features/editScene/systems/CellGridSystem.cpp +++ b/src/features/editScene/systems/CellGridSystem.cpp @@ -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; diff --git a/src/features/editScene/systems/DoorBuilder.cpp b/src/features/editScene/systems/DoorBuilder.cpp index e51cde4..4bc8231 100644 --- a/src/features/editScene/systems/DoorBuilder.cpp +++ b/src/features/editScene/systems/DoorBuilder.cpp @@ -16,24 +16,15 @@ #include #include #include -#include #include #include -/* 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 ¢er, - 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 ¢er, * 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 ¢er, 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 { diff --git a/src/features/editScene/systems/DoorBuilder.hpp b/src/features/editScene/systems/DoorBuilder.hpp index a16feb4..e0da887 100644 --- a/src/features/editScene/systems/DoorBuilder.hpp +++ b/src/features/editScene/systems/DoorBuilder.hpp @@ -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;