Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions src/OpenApparatus.Core/Geometry/BoundaryWallBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ static MeshData EmptyResult()

static MeshData BuildClosed(Adjacency adj, float t, float h)
{
var slab = SlabFrame.From(adj.SharedSegment, t);
// Extend the slab half a thickness past each end so perpendicular walls
// overlap at corners instead of leaving a t/2 square gap.
var slab = SlabFrame.From(Extend(adj.SharedSegment, t * 0.5f), t);
var b = new MeshDataBuilder();
EmitClosedWallFaces(b, slab, h);
b.EnsureSubmeshCount(SubmeshIndex.Count);
Expand Down Expand Up @@ -85,11 +87,16 @@ static void EmitClosedWallFaces(MeshDataBuilder b, SlabFrame slab, float h)

static MeshData BuildDoorway(Adjacency adj, float t, float h, Passage.Doorway door)
{
var slab = SlabFrame.From(adj.SharedSegment, t);
// Extend the slab half a thickness past each end (see BuildClosed).
float ext = t * 0.5f;
var slab = SlabFrame.From(Extend(adj.SharedSegment, ext), t);
const float EPS = 1e-5f;

// Sort openings by offset and validate fit + non-overlap.
var openings = new List<Opening>(door.Openings);
// Sort openings by offset and validate fit + non-overlap. Offsets shift
// by the extension because the slab now starts ext before the segment.
var openings = new List<Opening>(door.Openings.Count);
foreach (var op in door.Openings)
openings.Add(op.With(offsetAlongEdge: op.OffsetAlongEdge + ext));
openings.Sort((a, c) => a.OffsetAlongEdge.CompareTo(c.OffsetAlongEdge));
for (int i = 0; i < openings.Count; i++)
{
Expand Down Expand Up @@ -254,6 +261,14 @@ static void EmitBottomStrip(MeshDataBuilder b, SlabFrame slab, float xStart, flo
slab.Corner(false, xStart, 0f));
}

/// <summary>Lengthens a segment by <paramref name="d"/> at each end, keeping
/// its direction — used to overlap walls at corners.</summary>
static EdgeSegment Extend(EdgeSegment seg, float d)
{
var dir = seg.Direction;
return new EdgeSegment(seg.Start - dir * d, seg.End + dir * d);
}

// -------------------- Slab frame helper --------------------

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,21 +106,17 @@ public void DoorwayPassage_VertexCount_Matches15FacesAt4VertsEach()
}

[Fact]
public void DoorwayFlushWithStart_OmitsLeftJambAndLeftBottomAndLeftTunnelSide()
public void DoorwayAtSegmentStart_StillGetsLeftJamb_FromCornerExtension()
{
// doorOffset=0 → no left jamb on either side, no left bottom rim.
// Tunnel left side stays (still bounds the door); right side stays; ceiling stays.
// Faces: 2 (right-jamb each side) + 1 (top) + 1 (right bottom only) +
// 2 (caps) + 3 (tunnel left/right/ceiling) = 9. Wait — both tunnel sides
// are still emitted because the door has finite width with two side walls.
// Reconsider: 2 (right jamb each side) + 1 (top) + 1 (right bottom only) + 2 (caps) +
// 3 (tunnel) + 0 (no lintel split) ... but lintel IS still present (door
// height < wall height). Lintel = 2 (one per side).
// Total: 2 + 2 + 1 + 1 + 2 + 3 = 11 faces.
// A doorway authored flush with the segment start (offset 0) is no
// longer flush with the rendered slab: each slab is extended half a
// thickness past its ends to close corner gaps, so a stub of wall sits
// to the left of the opening. The full jamb set is therefore emitted,
// giving the same 14 wall faces as an interior doorway.
var (_, _, adj) = MakeInternalAdjacency(
new Passage.Doorway(offsetAlongEdge: 0f, width: 0.4f, height: 2.2f));
var mesh = new BoundaryWallBuilder().Build(adj, 0.2f, 3f);
Assert.Equal(11 * 2, mesh.TriangleCount(SubmeshIndex.Walls));
Assert.Equal(14 * 2, mesh.TriangleCount(SubmeshIndex.Walls));
Assert.Equal(1 * 2, mesh.TriangleCount(SubmeshIndex.Floor));
}

Expand Down
Loading