Skip to content
Open
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
71 changes: 57 additions & 14 deletions src/Blocktavius.Core/AdditiveHillBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public I2DSampler<Elevation> BuildHill(Region region)
cliffs.Add(BuildOutsideCorner(corner, cliffEW, cliffNS));
}

Rect fullBounds = Rect.Union([region.MaybeBetterBounds], cliffs.Select(s => s.Bounds));
Rect fullBounds = Rect.Union([region.Bounds], cliffs.Select(s => s.Bounds));
var sampler = new MutableArray2D<Elevation>(fullBounds, new Elevation(-1));

foreach (var cliff in cliffs)
Expand Down Expand Up @@ -120,11 +120,29 @@ private I2DSampler<Elevation> BuildOutsideCorner(Corner corner, EdgeCliff ewItem
int cornerSize = Math.Max(ewItem.MainCliff.Bounds.Size.Z, nsItem.MainCliff.Bounds.Size.Z);
var theSquare = new Rect(XZ.Zero, new XZ(cornerSize, cornerSize));

// The meeting point is where edges originally met (and where the corner cliff goes)
var meetingPoint = corner.MeetingPoint();

// Determine which end of each edge this corner is at, and slice accordingly
bool ewAtStart = ewItem.Edge.Start == meetingPoint;
bool nsAtStart = nsItem.Edge.Start == meetingPoint;
// Edges may have been moved, so check against original meeting point with compensation
var ewEdge = ewItem.Edge;
var nsEdge = nsItem.Edge;

// Determine which end of each edge is at the corner (accounting for movement)
var ewComp = ewEdge.InsideDirection switch
{
CardinalDirection.North => new XZ(0, 1),
CardinalDirection.West => new XZ(1, 0),
_ => XZ.Zero
};
var nsComp = nsEdge.InsideDirection switch
{
CardinalDirection.North => new XZ(0, 1),
CardinalDirection.West => new XZ(1, 0),
_ => XZ.Zero
};

bool ewAtStart = ewEdge.Start.Add(ewComp) == meetingPoint;
bool nsAtStart = nsEdge.Start.Add(nsComp) == meetingPoint;

bool ewLeft = ewItem.Edge.InsideDirection == CardinalDirection.East ? ewAtStart : !ewAtStart;
bool nsLeft = nsItem.Edge.InsideDirection == CardinalDirection.North ? nsAtStart : !nsAtStart;
Expand Down Expand Up @@ -168,9 +186,9 @@ private I2DSampler<Elevation> BuildOutsideCorner(Corner corner, EdgeCliff ewItem
// Combine using min (lower elevation wins at outside corners)
foreach (var xz in theSquare.Enumerate())
{
var elevEW = sliceEW.Sample(xz);
var elevNS = sliceNS.Sample(xz);
result.Put(xz, new Elevation(Math.Min(elevEW.Y, elevNS.Y)));
var elevEW = sliceEW.Sample(xz).Y;
var elevNS = sliceNS.Sample(xz).Y;
result.Put(xz, new Elevation(Math.Min(elevEW, elevNS)));
}

// Translate to final position
Expand Down Expand Up @@ -200,18 +218,43 @@ private static I2DSampler<T> Rotate<T>(Edge edge, I2DSampler<T> sampler)
private static I2DSampler<Elevation> TransformMainCliff(EdgeCliff ec)
{
var (edge, mainCliff) = (ec.Edge, ec.MainCliff);
// Edges are now inside the region. Cliffs need to be placed 1 step outside (opposite to InsideDirection).

// Step 1: Rotate the cliff to face the correct direction
// Step 2: Move it 1 step opposite to InsideDirection to place it outside
// Step 3: Adjust for the cliff's depth (rotated bounds may have shifted origin)

XZ outsideOffset = edge.InsideDirection switch
{
CardinalDirection.North => new XZ(0, 1), // Move south (opposite of north)
CardinalDirection.South => new XZ(0, -1), // Move north (opposite of south)
CardinalDirection.East => new XZ(-1, 0), // Move west (opposite of east)
CardinalDirection.West => new XZ(1, 0), // Move east (opposite of west)
_ => throw new Exception($"Assert fail: {edge.InsideDirection}")
};

switch (edge.InsideDirection)
{
case CardinalDirection.North:
return mainCliff.TranslateTo(edge.Start);
case CardinalDirection.South:
return mainCliff.Rotate(180)
.TranslateTo(edge.Start.Add(0, -mainCliff.Bounds.Size.Z));
case CardinalDirection.North:
// For N/S edges, cliff extends in Z direction
// Inside=South (North edge): cliff extends north, needs 180° rotation and depth adjustment
// Inside=North (South edge): cliff extends south, no rotation needed
var rotation = edge.InsideDirection == CardinalDirection.South ? 180 : 0;
var zAdjust = rotation == 180 ? -(mainCliff.Bounds.Size.Z - 1) : 0;
var rotated = mainCliff.Rotate(rotation);
var target = edge.Start.Add(outsideOffset).Add(0, zAdjust);
return rotated.TranslateTo(target);
case CardinalDirection.East:
return mainCliff.Rotate(90)
.TranslateTo(edge.Start.Add(-mainCliff.Bounds.Size.Z, 0));
case CardinalDirection.West:
return mainCliff.Rotate(270).TranslateTo(edge.Start);
// For E/W edges, cliff extends in X direction
// Inside=East (West edge): cliff extends west, needs 90° rotation and depth adjustment
// Inside=West (East edge): cliff extends east, needs 270° rotation
rotation = edge.InsideDirection == CardinalDirection.East ? 90 : 270;
var xAdjust = rotation == 90 ? -(mainCliff.Bounds.Size.Z - 1) : 0;
rotated = mainCliff.Rotate(rotation);
target = edge.Start.Add(outsideOffset).Add(xAdjust, 0);
return rotated.TranslateTo(target);
default:
throw new Exception($"Assert fail: {edge.InsideDirection}");
}
Expand Down
4 changes: 2 additions & 2 deletions src/Blocktavius.Core/Class1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,9 @@ private static Rect DoUnion(params IEnumerable<Rect>[] seqs)

public IEnumerable<XZ> Enumerate()
{
for (int x = start.X; x < end.X; x++)
for (int z = start.Z; z < end.Z; z++)
{
for (int z = start.Z; z < end.Z; z++)
for (int x = start.X; x < end.X; x++)
{
yield return new XZ(x, z);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Blocktavius.Core/CliffBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface ILayer
private readonly PRNG prng;

// TODO these should all be configurable:
const int minFenceLength = 1;
const int minFenceLength = 2;
const int maxFenceLength = 8;
const int maxNudge = 4;
const int maxLaneCount = 5;
Expand Down
162 changes: 162 additions & 0 deletions src/Blocktavius.Core/Region2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace Blocktavius.Core;

sealed class Region2
{
sealed class EdgeInfo
{
public required XZ TileMin { get; init; }
public required XZ TileMax { get; init; }
public required Direction InsideDirection { get; init; }

// Mutation! The following are computed after we find all the edges first.
// We only care about inside corners; outside corners just naturally work.
public EdgeInfo? InsideCornerMin { get; set; } // The edge with which TileMin forms an inside corner
public EdgeInfo? InsideCornerMax { get; set; } // The edge with which TileMax forms an inside corner
public EdgeInfo? OutsideCornerMin { get; set; } // The edge with which TileMin forms an outside corner
public EdgeInfo? OutsideCornerMax { get; set; } // The edge with which TileMax forms an outside corner
}

I2DSampler<bool> tileGrid = null!; // TODO pass it in
List<EdgeInfo> collectedEdges = new();

sealed class Check
{
public readonly HashSet<XZ> Visited = new();
public required Direction WalkDir { get; init; }
public required Direction InsideDir { get; init; }
}

public void BuildRegion()
{
// First, find all the edges.
// We walk south and east, which matches the direction of the iteration.
// So as soon as we find the min (north or west) point of an edge, we immedately
// walk as far as we can go, build the complete edge, and add all the tiles involved
// to the hash set so we don't create shorter versions of the same edge in the future.
var checkW = new Check() { InsideDir = Direction.West, WalkDir = Direction.South };
var checkE = new Check() { InsideDir = Direction.East, WalkDir = Direction.South };
var checkN = new Check() { InsideDir = Direction.North, WalkDir = Direction.East };
var checkS = new Check() { InsideDir = Direction.South, WalkDir = Direction.East };

foreach (var xz in tileGrid.Bounds.Enumerate())
{
DoCheck(checkW, xz);
DoCheck(checkE, xz);
DoCheck(checkN, xz);
DoCheck(checkS, xz);
}

// Second, find the inside corners.
// This method mutates the items in the list we pass here:
FindCorners(collectedEdges);
}

private void DoCheck(Check check, XZ start)
{
var (visited, walkDir, insideDir) = (check.Visited, check.WalkDir, check.InsideDir);
var outsideStep = insideDir.Step.Scale(-1);

if (visited.Contains(start))
{
return;
}

var tiles = start.Walk(walkDir, int.MaxValue)
.TakeWhile(xz => tileGrid.Sample(xz) && !tileGrid.Sample(xz.Add(outsideStep)))
.ToList();

if (tiles.Count > 0)
{
var edge = new EdgeInfo()
{
TileMin = tiles.First(),
TileMax = tiles.Last(),
InsideDirection = insideDir,
};
collectedEdges.Add(edge);
foreach (var xz in tiles)
{
visited.Add(xz);
}
}
}

private static void FindCorners(IReadOnlyList<EdgeInfo> edges)
{
// We will check each vertical edge, looking for the possible horizontal partners it might have.
// This means we don't have to check horizontal edges explicitly.
var horizontalEdges = edges.Where(e => e.InsideDirection.Step.X == 0);
var verticalEdges = edges.Where(e => e.InsideDirection.Step.Z == 0);

// The combination of (TileMin, InsideDirection) uniquely identifies an edge.
// The same is true of (TileMax, InsideDirection).
var minLookup = horizontalEdges.ToDictionary(e => (e.TileMin, e.InsideDirection));
var maxLookup = horizontalEdges.ToDictionary(e => (e.TileMax, e.InsideDirection));

foreach (var vert in verticalEdges)
{
var top = vert.TileMin;
var bottom = vert.TileMax;

if (vert.InsideDirection == Direction.West)
{
// Find inside corners, horizontal edges that start at vert.top or vert.bottom (adjusted)
if (minLookup.TryGetValue((top.Add(1, -1), Direction.North), out var topRight))
{
vert.InsideCornerMin = topRight;
topRight.InsideCornerMin = vert;
}
if (minLookup.TryGetValue((bottom.Add(1, 1), Direction.South), out var bottomRight))
{
vert.InsideCornerMax = bottomRight;
bottomRight.InsideCornerMin = vert;
}

// Find outside corners, horizontal edges that start at vert.top or vert.bottom
if (maxLookup.TryGetValue((top, Direction.South), out var topLeft))
{
vert.OutsideCornerMin = topLeft;
topLeft.OutsideCornerMax = vert;
}
if (maxLookup.TryGetValue((bottom, Direction.North), out var bottomLeft))
{
vert.OutsideCornerMax = bottomLeft;
bottomLeft.OutsideCornerMax = vert;
}
}
if (vert.InsideDirection == Direction.East)
{
// Find inside corners, horiztonal edges that end at vert.top or vert.bottom (adjusted)
if (maxLookup.TryGetValue((top.Add(-1, -1), Direction.North), out var topLeft))
{
vert.InsideCornerMin = topLeft;
topLeft.InsideCornerMax = vert;
}
if (maxLookup.TryGetValue((bottom.Add(-1, 1), Direction.South), out var bottomLeft))
{
vert.InsideCornerMax = bottomLeft;
bottomLeft.InsideCornerMax = vert;
}

// Find outside corners, horizontal edges that start at vert.top or vert.bottom
if (minLookup.TryGetValue((top, Direction.South), out var topRight))
{
vert.OutsideCornerMin = topRight;
topRight.OutsideCornerMin = vert;
}
if (minLookup.TryGetValue((bottom, Direction.North), out var bottomRight))
{
vert.OutsideCornerMax = bottomRight;
bottomRight.OutsideCornerMin = vert;
}
}
}
}
}
Loading