research(step2e): 8 parameter profiles, worst 570 ms -- speed gate met, look NOT yet accepted
Editor-only, default off. Seeded chamber graph (jittered XY grid, spanning tree, optional loops, tunnels, shafts, pillars, ledges, spires, floating masses, basins), 8 profiles, four detail bands at every surface-affecting 25 cm sample, sound brick proofs. 259^3 ring, six workers, medians of 3: 404-570 ms (worst TerracedBasin 570.0 ms). Detail, seam, bound-audit (0 violations, undersized control caught), determinism (1 vs 6 workers) and TraversalSeeds all pass; live tests untouched. Not accepted yet: the profiles are 81-97% open air. In the slices a "TightNetwork" is a large void with scattered solid blobs (rooms of 0.5-0.76 x pitch overlap and merge), and the finest detail band shows as salt-and-pepper speckle with 22-109 separate 1 m components per ring: fast partly because air bricks skip, and the look needs the owner's judgement. Also missing vs live: maze, flat/plain, crystal-chamber, surface-world and underwater grammars; water geometry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
f8a46ecf8d
commit
0a7492ffab
38 files changed
+1761
-186
No files matched your search
@@ -55,21 +55,21 @@ namespace
|
||||
};
|
||||
|
||||
const FParameterProfile GParameterProfiles[] = {
|
||||
{ TEXT("OpenVaults"), { 176.0f, 0.34f, 0.45f, 340.0f, 12.0f, 40.0f, 36.0f,
|
||||
{ TEXT("OpenVaults"), { 176.0f, 0.62f, 0.76f, 340.0f, 12.0f, 40.0f, 36.0f,
|
||||
0.35f, 0.14f, 22.0f, 0.04f, 0.12f, 0.08f, 0.15f, 0.00f, 0.0f, 0.50f } },
|
||||
{ TEXT("TightNetwork"), { 104.0f, 0.28f, 0.42f, 112.0f, 8.0f, 20.0f, 22.0f,
|
||||
{ TEXT("TightNetwork"), { 104.0f, 0.52f, 0.64f, 256.0f, 8.0f, 20.0f, 22.0f,
|
||||
0.12f, 0.03f, 14.0f, 0.06f, 0.08f, 0.02f, 0.12f, 0.00f, 0.0f, 0.50f } },
|
||||
{ TEXT("ChamberHalls"), { 144.0f, 0.38f, 0.45f, 320.0f, 18.0f, 34.0f, 32.0f,
|
||||
{ TEXT("ChamberHalls"), { 144.0f, 0.55f, 0.72f, 320.0f, 18.0f, 34.0f, 32.0f,
|
||||
0.28f, 0.18f, 22.0f, 0.08f, 0.22f, 0.10f, 0.22f, 0.05f, 20.0f, 0.40f } },
|
||||
{ TEXT("ShaftForest"), { 128.0f, 0.32f, 0.43f, 176.0f, 12.0f, 26.0f, 28.0f,
|
||||
0.12f, 0.55f, 20.0f, 0.05f, 0.18f, 0.04f, 0.20f, 0.00f, 0.0f, 0.50f } },
|
||||
{ TEXT("FloatingArchipelago"), { 136.0f, 0.37f, 0.45f, 192.0f, 10.0f, 28.0f, 28.0f,
|
||||
{ TEXT("ShaftForest"), { 128.0f, 0.50f, 0.62f, 272.0f, 12.0f, 26.0f, 28.0f,
|
||||
0.12f, 0.92f, 28.0f, 0.05f, 0.18f, 0.04f, 0.20f, 0.00f, 0.0f, 0.50f } },
|
||||
{ TEXT("FloatingArchipelago"), { 136.0f, 0.55f, 0.68f, 216.0f, 10.0f, 28.0f, 28.0f,
|
||||
0.15f, 0.10f, 16.0f, 0.05f, 0.10f, 0.72f, 0.28f, 0.00f, 0.0f, 0.50f } },
|
||||
{ TEXT("PillaredCathedral"), { 160.0f, 0.38f, 0.45f, 248.0f, 12.0f, 32.0f, 34.0f,
|
||||
{ TEXT("PillaredCathedral"), { 160.0f, 0.55f, 0.72f, 248.0f, 12.0f, 32.0f, 34.0f,
|
||||
0.25f, 0.08f, 18.0f, 0.72f, 0.30f, 0.08f, 0.45f, 0.00f, 0.0f, 0.50f } },
|
||||
{ TEXT("TerracedBasin"), { 136.0f, 0.34f, 0.45f, 216.0f, 14.0f, 28.0f, 30.0f,
|
||||
{ TEXT("TerracedBasin"), { 136.0f, 0.52f, 0.65f, 264.0f, 14.0f, 28.0f, 30.0f,
|
||||
0.20f, 0.10f, 18.0f, 0.10f, 0.84f, 0.05f, 0.25f, 0.85f, 48.0f, 0.30f } },
|
||||
{ TEXT("HyperComplex"), { 96.0f, 0.30f, 0.45f, 192.0f, 20.0f, 30.0f, 28.0f,
|
||||
{ TEXT("HyperComplex"), { 96.0f, 0.60f, 0.74f, 280.0f, 20.0f, 30.0f, 28.0f,
|
||||
0.40f, 0.55f, 20.0f, 0.45f, 0.68f, 0.50f, 0.75f, 0.42f, 28.0f, 0.40f } },
|
||||
};
|
||||
|
||||
@@ -82,6 +82,11 @@ namespace
|
||||
float PadRadius = 0.0f;
|
||||
float BlendRadius = LandingFloorBlendVoxels;
|
||||
FVector ApproachEnd = FVector::ZeroVector;
|
||||
bool bGeneratedAnchorPrepared = false;
|
||||
FVector GeneratedAnchorStart = FVector::ZeroVector;
|
||||
FVector GeneratedAnchorDelta = FVector::ZeroVector;
|
||||
float GeneratedAnchorLengthSquared = 0.0f;
|
||||
float GeneratedAnchorReach = 0.0f;
|
||||
};
|
||||
|
||||
struct FVerticalShaftNode
|
||||
@@ -147,6 +152,8 @@ namespace
|
||||
TArray<FLandingSite> Landings;
|
||||
};
|
||||
|
||||
void PrepareParameterizedAnchors(FStratePlan& Plan);
|
||||
|
||||
struct FThreadPlanCache
|
||||
{
|
||||
const UVoxelStrateManager* Manager = nullptr;
|
||||
@@ -159,6 +166,7 @@ namespace
|
||||
};
|
||||
|
||||
thread_local FThreadPlanCache GPlanCache;
|
||||
thread_local const FStratePlan* GParameterizedRoomCellCachePlan = nullptr;
|
||||
thread_local FThreadShaftEdgeCache GShaftEdgeCache;
|
||||
|
||||
FORCEINLINE uint32 Mix(uint32 Value)
|
||||
@@ -477,6 +485,7 @@ namespace
|
||||
if (Manager == nullptr)
|
||||
{
|
||||
GPlanCache = FThreadPlanCache();
|
||||
GParameterizedRoomCellCachePlan = nullptr;
|
||||
return;
|
||||
}
|
||||
const uint64 Lifetime = Manager->GetCacheLifetimeId();
|
||||
@@ -492,6 +501,7 @@ namespace
|
||||
}
|
||||
|
||||
GPlanCache = FThreadPlanCache();
|
||||
GParameterizedRoomCellCachePlan = nullptr;
|
||||
GPlanCache.Manager = Manager;
|
||||
GPlanCache.ManagerLifetimeId = Lifetime;
|
||||
GPlanCache.LayoutVersion = Version;
|
||||
@@ -566,10 +576,10 @@ namespace
|
||||
Plan.Generation.RoomPitchVoxels = FMath::Max(
|
||||
Plan.Generation.RoomPitchVoxels, 80.0f);
|
||||
Plan.Generation.MinimumRoomRadiusFraction = FMath::Clamp(
|
||||
Plan.Generation.MinimumRoomRadiusFraction, 0.20f, 0.41f);
|
||||
Plan.Generation.MinimumRoomRadiusFraction, 0.20f, 0.72f);
|
||||
Plan.Generation.MaximumRoomRadiusFraction = FMath::Clamp(
|
||||
Plan.Generation.MaximumRoomRadiusFraction,
|
||||
Plan.Generation.MinimumRoomRadiusFraction, 0.45f);
|
||||
Plan.Generation.MinimumRoomRadiusFraction, 0.80f);
|
||||
Plan.Generation.LoopProbability = FMath::Clamp(
|
||||
Plan.Generation.LoopProbability, 0.0f, 0.8f);
|
||||
Plan.Generation.VerticalShaftProbability = FMath::Clamp(
|
||||
@@ -718,7 +728,8 @@ namespace
|
||||
Plan.FloorZ = FMath::Clamp(
|
||||
static_cast<float>(FloorSum / FloorCount), Bottom + 16.0f, Top - 72.0f);
|
||||
Plan.CeilingZ = Plan.FloorZ + MinimumCaveHeight;
|
||||
Plan.GenerationFloorZ = Plan.FloorZ;
|
||||
// Parameterized chambers keep their profile-defined vertical span. The
|
||||
// passage landing is connected into that graph by its generated anchor.
|
||||
}
|
||||
}
|
||||
for (FLandingSite& Site : Plan.Landings)
|
||||
@@ -765,6 +776,10 @@ namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
for (FStratePlan& Plan : GPlanCache.Strates)
|
||||
{
|
||||
PrepareParameterizedAnchors(Plan);
|
||||
}
|
||||
}
|
||||
|
||||
struct FGeneratedCaveCell
|
||||
@@ -788,6 +803,30 @@ namespace
|
||||
float SpireLength = 0.0f;
|
||||
float SpireRadius = 0.0f;
|
||||
uint8 FeatureFlags = 0;
|
||||
uint8 OpenTunnelEdges = 0;
|
||||
};
|
||||
|
||||
struct FParameterizedColumnContext
|
||||
{
|
||||
FGeneratedCaveCell GeneratedCells[9];
|
||||
const FGeneratedCaveCell* Cells[9] = {};
|
||||
float RoomDistanceSquared[9] = {};
|
||||
float RoomRadii[9] = {};
|
||||
float RoomHorizontals[9] = {};
|
||||
float RoomFloors[9] = {};
|
||||
float PillarHorizontals[9] = {};
|
||||
float FloatingXYDistanceSquared[9][2] = {};
|
||||
float SpireBaseZ[9] = {};
|
||||
float SpireTipZ[9] = {};
|
||||
float SpireBaseTopZ[9] = {};
|
||||
float SpireTipBottomZ[9] = {};
|
||||
float TunnelT[4] = {};
|
||||
float TunnelHorizontal[4] = {};
|
||||
float LandingDistanceSquared[8] = {};
|
||||
float LandingDistance[8] = {};
|
||||
float LandingBridgeT[8] = {};
|
||||
float LandingBridgeHorizontal[8] = {};
|
||||
bool bHasLandingXYCache = false;
|
||||
};
|
||||
|
||||
constexpr uint8 HasPillarFlag = 1u << 0;
|
||||
@@ -795,6 +834,20 @@ namespace
|
||||
constexpr uint8 HasFloatingFlag = 1u << 2;
|
||||
constexpr uint8 HasSpireFlag = 1u << 3;
|
||||
constexpr uint8 HasBasinFlag = 1u << 4;
|
||||
constexpr uint8 HasVerticalShaftFlag = 1u << 5;
|
||||
constexpr uint8 TunnelToNegativeY = 1u << 0;
|
||||
constexpr uint8 TunnelToPositiveY = 1u << 1;
|
||||
constexpr uint8 TunnelToNegativeX = 1u << 2;
|
||||
constexpr uint8 TunnelToPositiveX = 1u << 3;
|
||||
|
||||
thread_local TArray<FGeneratedCaveCell> GParameterizedRoomCellCache;
|
||||
thread_local FIntPoint GParameterizedRoomCellCacheMin = FIntPoint::ZeroValue;
|
||||
thread_local FIntPoint GParameterizedRoomCellCacheSize = FIntPoint::ZeroValue;
|
||||
|
||||
FIntPoint GeneratedParent(const FStratePlan& Plan, int32 CellX, int32 CellY);
|
||||
bool IsGeneratedEdgeOpen(
|
||||
const FStratePlan& Plan, int32 AX, int32 AY, int32 BX, int32 BY);
|
||||
FORCEINLINE FIntPoint GenerationCellAt(const FStratePlan& Plan, float X, float Y);
|
||||
|
||||
FORCEINLINE float ParameterRoll(uint32 Hash, uint32 Salt)
|
||||
{
|
||||
@@ -829,6 +882,27 @@ namespace
|
||||
Cell.RadiusX = Pitch * RadiusFraction;
|
||||
Cell.RadiusY = Cell.RadiusX * FMath::Lerp(
|
||||
0.82f, 0.98f, ParameterRoll(Hash, 0x27C5u));
|
||||
if (VoxelHash::ToFloat01(Hash3(CellX, CellY, 0x53, Plan.GenerationSeed))
|
||||
< P.VerticalShaftProbability)
|
||||
{
|
||||
Cell.FeatureFlags |= HasVerticalShaftFlag;
|
||||
}
|
||||
if (IsGeneratedEdgeOpen(Plan, CellX, CellY, CellX, CellY - 1))
|
||||
{
|
||||
Cell.OpenTunnelEdges |= TunnelToNegativeY;
|
||||
}
|
||||
if (IsGeneratedEdgeOpen(Plan, CellX, CellY, CellX, CellY + 1))
|
||||
{
|
||||
Cell.OpenTunnelEdges |= TunnelToPositiveY;
|
||||
}
|
||||
if (IsGeneratedEdgeOpen(Plan, CellX, CellY, CellX - 1, CellY))
|
||||
{
|
||||
Cell.OpenTunnelEdges |= TunnelToNegativeX;
|
||||
}
|
||||
if (IsGeneratedEdgeOpen(Plan, CellX, CellY, CellX + 1, CellY))
|
||||
{
|
||||
Cell.OpenTunnelEdges |= TunnelToPositiveX;
|
||||
}
|
||||
Cell.FloorZ = Plan.GenerationFloorZ
|
||||
+ (ParameterRoll(Hash, 0xF103u) * 2.0f - 1.0f) * P.FloorReliefVoxels;
|
||||
Cell.CeilingZ = Cell.FloorZ + P.RoomHeightVoxels;
|
||||
@@ -880,6 +954,42 @@ namespace
|
||||
return Cell;
|
||||
}
|
||||
|
||||
void PrepareParameterizedAnchors(FStratePlan& Plan)
|
||||
{
|
||||
const float AnchorReach = Plan.Generation.RoomPitchVoxels * 1.25f
|
||||
+ FMath::Max(32.0f, Plan.Generation.TunnelRadiusVoxels);
|
||||
for (FLandingSite& Site : Plan.Landings)
|
||||
{
|
||||
const FIntPoint LandingCell = GenerationCellAt(
|
||||
Plan, Site.Position.X, Site.Position.Y);
|
||||
FGeneratedCaveCell NearestCell;
|
||||
float BestDistanceSquared = FLT_MAX;
|
||||
for (int32 CellY = -1; CellY <= 1; ++CellY)
|
||||
for (int32 CellX = -1; CellX <= 1; ++CellX)
|
||||
{
|
||||
const FGeneratedCaveCell Candidate = BuildGeneratedCell(
|
||||
Plan, LandingCell.X + CellX, LandingCell.Y + CellY);
|
||||
const float DX = Candidate.CenterX - Site.Position.X;
|
||||
const float DY = Candidate.CenterY - Site.Position.Y;
|
||||
const float DistanceSquared = DX * DX + DY * DY;
|
||||
if (DistanceSquared < BestDistanceSquared)
|
||||
{
|
||||
BestDistanceSquared = DistanceSquared;
|
||||
NearestCell = Candidate;
|
||||
}
|
||||
}
|
||||
Site.GeneratedAnchorStart = FVector(
|
||||
Site.Position.X, Site.Position.Y, Site.FloorZ + 24.0f);
|
||||
const FVector AnchorEnd(
|
||||
NearestCell.CenterX, NearestCell.CenterY,
|
||||
NearestCell.FloorZ + Plan.Generation.TunnelHalfHeightVoxels);
|
||||
Site.GeneratedAnchorDelta = AnchorEnd - Site.GeneratedAnchorStart;
|
||||
Site.GeneratedAnchorLengthSquared = Site.GeneratedAnchorDelta.SizeSquared();
|
||||
Site.GeneratedAnchorReach = AnchorReach;
|
||||
Site.bGeneratedAnchorPrepared = true;
|
||||
}
|
||||
}
|
||||
|
||||
FORCEINLINE float SmoothMinimum(float A, float B, float K)
|
||||
{
|
||||
const float H = FMath::Max(K - FMath::Abs(A - B), 0.0f) / K;
|
||||
@@ -903,72 +1013,81 @@ namespace
|
||||
}
|
||||
|
||||
FORCEINLINE float GeneratedRoomCell(
|
||||
const FStratePlan& Plan, const FGeneratedCaveCell& Cell,
|
||||
float X, float Y, float Z)
|
||||
const FGeneratedCaveCell& Cell,
|
||||
float Z, float Radius, float Horizontal,
|
||||
const FParameterizedColumnContext& Column, int32 CellIndex)
|
||||
{
|
||||
const float DX = X - Cell.CenterX;
|
||||
const float DY = Y - Cell.CenterY;
|
||||
const float Radius = FMath::Sqrt(DX * DX + DY * DY);
|
||||
const float Horizontal = Cell.RadiusX - Radius;
|
||||
float FloorZ = Cell.FloorZ;
|
||||
if ((Cell.FeatureFlags & HasBasinFlag) != 0)
|
||||
{
|
||||
FloorZ += Plan.Generation.BasinDepthVoxels
|
||||
* FMath::Clamp(Radius / FMath::Max(Cell.RadiusX, 1.0f), 0.0f, 1.0f);
|
||||
}
|
||||
const float FloorZ = Column.RoomFloors[CellIndex];
|
||||
const float Vertical = FMath::Min(Z - FloorZ, Cell.CeilingZ - Z);
|
||||
float Room = SmoothMinimum(Horizontal, Vertical, 8.0f);
|
||||
if (Room > 0.0f && (Cell.FeatureFlags & HasPillarFlag) != 0)
|
||||
if ((Cell.FeatureFlags & HasPillarFlag) != 0 && Room > -14.0f)
|
||||
{
|
||||
const float PX = X - Cell.PillarX;
|
||||
const float PY = Y - Cell.PillarY;
|
||||
const float Pillar = FMath::Max3(
|
||||
FMath::Sqrt(PX * PX + PY * PY) - Cell.PillarRadius,
|
||||
Column.PillarHorizontals[CellIndex],
|
||||
Cell.FloorZ - Z, Z - Cell.CeilingZ);
|
||||
Room = FMath::Min(Room, Pillar);
|
||||
if (Pillar < Room)
|
||||
{
|
||||
Room = Pillar;
|
||||
}
|
||||
}
|
||||
if ((Cell.FeatureFlags & HasLedgeFlag) != 0
|
||||
&& Horizontal >= -2.0f && FMath::Abs(Z - Cell.LedgeZ) <= 3.0f)
|
||||
if ((Cell.FeatureFlags & HasLedgeFlag) != 0)
|
||||
{
|
||||
const float Inner = Cell.RadiusX * 0.58f;
|
||||
const float Outer = Cell.RadiusX + 1.0f;
|
||||
const float Ledge = FMath::Max3(
|
||||
Inner - Radius, Radius - Outer, FMath::Abs(Z - Cell.LedgeZ) - 2.5f);
|
||||
Room = FMath::Min(Room, Ledge);
|
||||
if (Ledge < Room)
|
||||
{
|
||||
Room = Ledge;
|
||||
}
|
||||
}
|
||||
if (Room > 0.0f && (Cell.FeatureFlags & HasFloatingFlag) != 0)
|
||||
if ((Cell.FeatureFlags & HasFloatingFlag) != 0)
|
||||
{
|
||||
for (int32 Blob = 0; Blob < 2; ++Blob)
|
||||
{
|
||||
const float BX = X - Cell.FloatingX[Blob];
|
||||
const float BY = Y - Cell.FloatingY[Blob];
|
||||
if (Room <= -Cell.FloatingRadius[Blob])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const float BZ = Z - Cell.FloatingZ[Blob];
|
||||
const float BlobSolid = FMath::Sqrt(BX * BX + BY * BY + BZ * BZ)
|
||||
const float BlobSolid = FMath::Sqrt(
|
||||
Column.FloatingXYDistanceSquared[CellIndex][Blob] + BZ * BZ)
|
||||
- Cell.FloatingRadius[Blob];
|
||||
Room = FMath::Min(Room, BlobSolid);
|
||||
if (BlobSolid < Room)
|
||||
{
|
||||
Room = BlobSolid;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Room > 0.0f && (Cell.FeatureFlags & HasSpireFlag) != 0)
|
||||
if ((Cell.FeatureFlags & HasSpireFlag) != 0 && Room > -14.0f)
|
||||
{
|
||||
const float BaseZ = Cell.FloorZ + 2.0f;
|
||||
const float TipZ = FMath::Min(Cell.CeilingZ - 2.0f, BaseZ + Cell.SpireLength);
|
||||
if (Z >= BaseZ && Z <= TipZ)
|
||||
const float BaseZ = Column.SpireBaseZ[CellIndex];
|
||||
const float TipZ = Column.SpireTipZ[CellIndex];
|
||||
if (Z >= BaseZ || BaseZ - Z < Room)
|
||||
{
|
||||
const float T = (Z - BaseZ) / FMath::Max(TipZ - BaseZ, 1.0f);
|
||||
const float T = FMath::Clamp(
|
||||
(Z - BaseZ) / FMath::Max(TipZ - BaseZ, 1.0f), 0.0f, 1.0f);
|
||||
const float RadiusAtZ = FMath::Lerp(Cell.SpireRadius, 1.25f, T);
|
||||
const float Spire = FMath::Max3(
|
||||
Radius - RadiusAtZ, BaseZ - Z, Z - TipZ);
|
||||
Room = FMath::Min(Room, Spire);
|
||||
if (Spire < Room)
|
||||
{
|
||||
Room = Spire;
|
||||
}
|
||||
}
|
||||
const float BaseTop = Cell.CeilingZ - 2.0f;
|
||||
const float TipBottom = FMath::Max(Cell.FloorZ + 2.0f, BaseTop - Cell.SpireLength);
|
||||
if (Z >= TipBottom && Z <= BaseTop)
|
||||
const float BaseTop = Column.SpireBaseTopZ[CellIndex];
|
||||
const float TipBottom = Column.SpireTipBottomZ[CellIndex];
|
||||
if (Z <= BaseTop || Z - BaseTop < Room)
|
||||
{
|
||||
const float T = (BaseTop - Z) / FMath::Max(BaseTop - TipBottom, 1.0f);
|
||||
const float T = FMath::Clamp(
|
||||
(BaseTop - Z) / FMath::Max(BaseTop - TipBottom, 1.0f), 0.0f, 1.0f);
|
||||
const float RadiusAtZ = FMath::Lerp(Cell.SpireRadius, 1.25f, T);
|
||||
const float Stalactite = FMath::Max3(
|
||||
Radius - RadiusAtZ, Z - BaseTop, TipBottom - Z);
|
||||
Room = FMath::Min(Room, Stalactite);
|
||||
if (Stalactite < Room)
|
||||
{
|
||||
Room = Stalactite;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Room;
|
||||
@@ -1018,18 +1137,8 @@ namespace
|
||||
FORCEINLINE float GeneratedTunnelEdge(
|
||||
const FStratePlan& Plan,
|
||||
const FGeneratedCaveCell& A, const FGeneratedCaveCell& B,
|
||||
float X, float Y, float Z)
|
||||
float Z, float T, float Horizontal)
|
||||
{
|
||||
if (!IsGeneratedEdgeOpen(Plan, A.CellX, A.CellY, B.CellX, B.CellY))
|
||||
{
|
||||
return -FLT_MAX;
|
||||
}
|
||||
const float T = SegmentParameterXY(X, Y,
|
||||
FVector(A.CenterX, A.CenterY, 0.0f), FVector(B.CenterX, B.CenterY, 0.0f));
|
||||
const float Horizontal = Plan.Generation.TunnelRadiusVoxels
|
||||
- FMath::Sqrt(DistanceSquaredToSegmentXY(
|
||||
X, Y, FVector(A.CenterX, A.CenterY, 0.0f),
|
||||
FVector(B.CenterX, B.CenterY, 0.0f)));
|
||||
const float FloorZ = FMath::Lerp(A.FloorZ, B.FloorZ, T);
|
||||
const float Vertical = FMath::Min(
|
||||
Z - FloorZ,
|
||||
@@ -1037,66 +1146,267 @@ namespace
|
||||
return SmoothMinimum(Horizontal, Vertical, 5.0f);
|
||||
}
|
||||
|
||||
float EvaluateParameterizedMacro(
|
||||
const FStratePlan& Plan, float X, float Y, float Z, float* OutDetailScale)
|
||||
const FGeneratedCaveCell* FindParameterizedBrickRoomCell(
|
||||
const FStratePlan& Plan, const FParameterizedBrickContext* Context,
|
||||
int32 CellX, int32 CellY)
|
||||
{
|
||||
if (Context == nullptr || Context->PlanToken != &Plan
|
||||
|| Context->RoomCellCacheToken != GParameterizedRoomCellCache.GetData()
|
||||
|| GParameterizedRoomCellCachePlan != &Plan
|
||||
|| CellX < Context->RoomCellCacheMin.X
|
||||
|| CellY < Context->RoomCellCacheMin.Y
|
||||
|| CellX >= Context->RoomCellCacheMin.X + Context->RoomCellCacheSize.X
|
||||
|| CellY >= Context->RoomCellCacheMin.Y + Context->RoomCellCacheSize.Y)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
const int32 Index = (CellY - Context->RoomCellCacheMin.Y)
|
||||
* Context->RoomCellCacheSize.X + CellX - Context->RoomCellCacheMin.X;
|
||||
return GParameterizedRoomCellCache.IsValidIndex(Index)
|
||||
? &GParameterizedRoomCellCache[Index] : nullptr;
|
||||
}
|
||||
|
||||
void BuildParameterizedColumnContext(
|
||||
const FStratePlan& Plan, float X, float Y,
|
||||
const FParameterizedBrickContext* Context,
|
||||
FParameterizedColumnContext& OutColumn)
|
||||
{
|
||||
const FIntPoint BaseCell = GenerationCellAt(Plan, X, Y);
|
||||
FGeneratedCaveCell Cells[9];
|
||||
const bool bHasRoomCellCache = Context != nullptr
|
||||
&& Context->PlanToken == &Plan
|
||||
&& Context->RoomCellCacheToken == GParameterizedRoomCellCache.GetData()
|
||||
&& GParameterizedRoomCellCachePlan == &Plan;
|
||||
for (int32 CellY = -1; CellY <= 1; ++CellY)
|
||||
for (int32 CellX = -1; CellX <= 1; ++CellX)
|
||||
{
|
||||
const int32 Index = (CellY + 1) * 3 + CellX + 1;
|
||||
Cells[Index] = BuildGeneratedCell(
|
||||
Plan, BaseCell.X + CellX, BaseCell.Y + CellY);
|
||||
const int32 GeneratedX = BaseCell.X + CellX;
|
||||
const int32 GeneratedY = BaseCell.Y + CellY;
|
||||
if (bHasRoomCellCache)
|
||||
{
|
||||
const int32 CacheX = GeneratedX - Context->RoomCellCacheMin.X;
|
||||
const int32 CacheY = GeneratedY - Context->RoomCellCacheMin.Y;
|
||||
if (CacheX >= 0 && CacheY >= 0
|
||||
&& CacheX < Context->RoomCellCacheSize.X
|
||||
&& CacheY < Context->RoomCellCacheSize.Y)
|
||||
{
|
||||
const int32 CacheIndex = CacheY * Context->RoomCellCacheSize.X + CacheX;
|
||||
if (GParameterizedRoomCellCache.IsValidIndex(CacheIndex))
|
||||
{
|
||||
OutColumn.Cells[Index] = &GParameterizedRoomCellCache[CacheIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
OutColumn.Cells[Index] = FindParameterizedBrickRoomCell(
|
||||
Plan, Context, GeneratedX, GeneratedY);
|
||||
}
|
||||
if (OutColumn.Cells[Index] == nullptr)
|
||||
{
|
||||
OutColumn.GeneratedCells[Index] = BuildGeneratedCell(
|
||||
Plan, GeneratedX, GeneratedY);
|
||||
OutColumn.Cells[Index] = &OutColumn.GeneratedCells[Index];
|
||||
}
|
||||
const float DX = X - OutColumn.Cells[Index]->CenterX;
|
||||
const float DY = Y - OutColumn.Cells[Index]->CenterY;
|
||||
const float DistanceSquared = DX * DX + DY * DY;
|
||||
OutColumn.RoomDistanceSquared[Index] = DistanceSquared;
|
||||
OutColumn.RoomRadii[Index] = FMath::Sqrt(DistanceSquared);
|
||||
OutColumn.RoomHorizontals[Index] = OutColumn.Cells[Index]->RadiusX
|
||||
- OutColumn.RoomRadii[Index];
|
||||
|
||||
const FGeneratedCaveCell& Cell = *OutColumn.Cells[Index];
|
||||
OutColumn.RoomFloors[Index] = Cell.FloorZ;
|
||||
if ((Cell.FeatureFlags & HasBasinFlag) != 0)
|
||||
{
|
||||
OutColumn.RoomFloors[Index] += Plan.Generation.BasinDepthVoxels
|
||||
* FMath::Clamp(OutColumn.RoomRadii[Index]
|
||||
/ FMath::Max(Cell.RadiusX, 1.0f), 0.0f, 1.0f);
|
||||
}
|
||||
if ((Cell.FeatureFlags & HasPillarFlag) != 0)
|
||||
{
|
||||
const float PX = X - Cell.PillarX;
|
||||
const float PY = Y - Cell.PillarY;
|
||||
OutColumn.PillarHorizontals[Index] = FMath::Sqrt(PX * PX + PY * PY)
|
||||
- Cell.PillarRadius;
|
||||
}
|
||||
if ((Cell.FeatureFlags & HasFloatingFlag) != 0)
|
||||
{
|
||||
for (int32 Blob = 0; Blob < 2; ++Blob)
|
||||
{
|
||||
const float BX = X - Cell.FloatingX[Blob];
|
||||
const float BY = Y - Cell.FloatingY[Blob];
|
||||
OutColumn.FloatingXYDistanceSquared[Index][Blob] = BX * BX + BY * BY;
|
||||
}
|
||||
}
|
||||
if ((Cell.FeatureFlags & HasSpireFlag) != 0)
|
||||
{
|
||||
OutColumn.SpireBaseZ[Index] = Cell.FloorZ + 2.0f;
|
||||
OutColumn.SpireTipZ[Index] = FMath::Min(
|
||||
Cell.CeilingZ - 2.0f,
|
||||
OutColumn.SpireBaseZ[Index] + Cell.SpireLength);
|
||||
OutColumn.SpireBaseTopZ[Index] = Cell.CeilingZ - 2.0f;
|
||||
OutColumn.SpireTipBottomZ[Index] = FMath::Max(
|
||||
Cell.FloorZ + 2.0f,
|
||||
OutColumn.SpireBaseTopZ[Index] - Cell.SpireLength);
|
||||
}
|
||||
}
|
||||
|
||||
const FGeneratedCaveCell& Centre = *OutColumn.Cells[4];
|
||||
const int32 EdgeIndices[] = {1, 7, 3, 5};
|
||||
const uint8 EdgeFlags[] = {
|
||||
TunnelToNegativeY, TunnelToPositiveY,
|
||||
TunnelToNegativeX, TunnelToPositiveX};
|
||||
for (int32 EdgeIndex = 0; EdgeIndex < UE_ARRAY_COUNT(EdgeIndices); ++EdgeIndex)
|
||||
{
|
||||
OutColumn.TunnelHorizontal[EdgeIndex] = -FLT_MAX;
|
||||
if ((Centre.OpenTunnelEdges & EdgeFlags[EdgeIndex]) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const FGeneratedCaveCell& Next = *OutColumn.Cells[EdgeIndices[EdgeIndex]];
|
||||
const float ABX = Next.CenterX - Centre.CenterX;
|
||||
const float ABY = Next.CenterY - Centre.CenterY;
|
||||
const float LengthSquared = ABX * ABX + ABY * ABY;
|
||||
const float T = LengthSquared > 1.0e-8f
|
||||
? FMath::Clamp(((X - Centre.CenterX) * ABX
|
||||
+ (Y - Centre.CenterY) * ABY) / LengthSquared, 0.0f, 1.0f)
|
||||
: 0.0f;
|
||||
const float DX = X - (Centre.CenterX + T * ABX);
|
||||
const float DY = Y - (Centre.CenterY + T * ABY);
|
||||
OutColumn.TunnelT[EdgeIndex] = T;
|
||||
OutColumn.TunnelHorizontal[EdgeIndex] = Plan.Generation.TunnelRadiusVoxels
|
||||
- FMath::Sqrt(DX * DX + DY * DY);
|
||||
}
|
||||
|
||||
OutColumn.bHasLandingXYCache = Plan.Landings.Num() <= UE_ARRAY_COUNT(
|
||||
OutColumn.LandingDistanceSquared);
|
||||
if (OutColumn.bHasLandingXYCache)
|
||||
{
|
||||
const FLandingSite* Hub = Plan.LandingHubIndex != INDEX_NONE
|
||||
? &Plan.Landings[Plan.LandingHubIndex] : nullptr;
|
||||
for (int32 SiteIndex = 0; SiteIndex < Plan.Landings.Num(); ++SiteIndex)
|
||||
{
|
||||
const FLandingSite& Site = Plan.Landings[SiteIndex];
|
||||
const float DX = X - Site.Position.X;
|
||||
const float DY = Y - Site.Position.Y;
|
||||
const float DistanceSquared = DX * DX + DY * DY;
|
||||
OutColumn.LandingDistanceSquared[SiteIndex] = DistanceSquared;
|
||||
OutColumn.LandingDistance[SiteIndex] = FMath::Sqrt(DistanceSquared);
|
||||
OutColumn.LandingBridgeT[SiteIndex] = 0.0f;
|
||||
OutColumn.LandingBridgeHorizontal[SiteIndex] = -FLT_MAX;
|
||||
if (Hub != nullptr && Plan.Landings.Num() > 1
|
||||
&& SiteIndex != Plan.LandingHubIndex)
|
||||
{
|
||||
OutColumn.LandingBridgeT[SiteIndex] = SegmentParameterXY(
|
||||
X, Y, Site.Position, Hub->Position);
|
||||
OutColumn.LandingBridgeHorizontal[SiteIndex] = LandingBridgeRadius
|
||||
- FMath::Sqrt(DistanceSquaredToSegmentXY(
|
||||
X, Y, Site.Position, Hub->Position));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float EvaluateParameterizedMacro(
|
||||
const FStratePlan& Plan, float X, float Y, float Z, float* OutDetailScale,
|
||||
const FParameterizedBrickContext* Context = nullptr,
|
||||
const FParameterizedColumnContext* ColumnContext = nullptr)
|
||||
{
|
||||
FParameterizedColumnContext LocalColumn;
|
||||
if (ColumnContext == nullptr)
|
||||
{
|
||||
BuildParameterizedColumnContext(Plan, X, Y, Context, LocalColumn);
|
||||
ColumnContext = &LocalColumn;
|
||||
}
|
||||
|
||||
float Macro = -FLT_MAX;
|
||||
for (const FGeneratedCaveCell& Cell : Cells)
|
||||
{
|
||||
const float DX = X - Cell.CenterX;
|
||||
const float DY = Y - Cell.CenterY;
|
||||
const float Horizontal = Cell.RadiusX - FMath::Sqrt(DX * DX + DY * DY);
|
||||
if (Macro > -FLT_MAX * 0.5f && Horizontal < Macro - 6.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Macro = SmoothMaximum(Macro,
|
||||
GeneratedRoomCell(Plan, Cell, X, Y, Z), 6.0f);
|
||||
}
|
||||
const FGeneratedCaveCell& Centre = Cells[4];
|
||||
const int32 EdgeIndices[] = {1, 7, 3, 5};
|
||||
for (const int32 EdgeIndex : EdgeIndices)
|
||||
{
|
||||
Macro = SmoothMaximum(Macro,
|
||||
GeneratedTunnelEdge(Plan, Centre, Cells[EdgeIndex], X, Y, Z), 5.0f);
|
||||
}
|
||||
|
||||
const float Bottom = static_cast<float>(Plan.MinChunkZ * CHUNK_SIZE) + 8.0f;
|
||||
const float Top = static_cast<float>(Plan.MaxChunkZExclusive * CHUNK_SIZE) - 8.0f;
|
||||
for (const FGeneratedCaveCell& Cell : Cells)
|
||||
static constexpr int32 CellEvaluationOrder[] = {4, 1, 3, 5, 7, 0, 2, 6, 8};
|
||||
for (int32 OrderIndex = 0;
|
||||
OrderIndex < UE_ARRAY_COUNT(CellEvaluationOrder); ++OrderIndex)
|
||||
{
|
||||
const uint32 ShaftHash = Hash3(
|
||||
Cell.CellX, Cell.CellY, 0x53, Plan.GenerationSeed);
|
||||
if (VoxelHash::ToFloat01(ShaftHash) >= Plan.Generation.VerticalShaftProbability)
|
||||
const int32 Index = CellEvaluationOrder[OrderIndex];
|
||||
const FGeneratedCaveCell& Cell = *ColumnContext->Cells[Index];
|
||||
const float DistanceSquared = ColumnContext->RoomDistanceSquared[Index];
|
||||
if (Macro > -FLT_MAX * 0.5f)
|
||||
{
|
||||
const float MaximumDistance = Cell.RadiusX - Macro + 6.0f;
|
||||
if (MaximumDistance < 0.0f
|
||||
|| DistanceSquared > FMath::Square(MaximumDistance + 1.0e-3f))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const float VerticalUpperBound = FMath::Min(
|
||||
Z - ColumnContext->RoomFloors[Index], Cell.CeilingZ - Z);
|
||||
if (VerticalUpperBound <= Macro - 6.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Macro = SmoothMaximum(Macro,
|
||||
GeneratedRoomCell(Cell, Z,
|
||||
ColumnContext->RoomRadii[Index],
|
||||
ColumnContext->RoomHorizontals[Index],
|
||||
*ColumnContext, Index), 6.0f);
|
||||
}
|
||||
const FGeneratedCaveCell& Centre = *ColumnContext->Cells[4];
|
||||
const int32 EdgeIndices[] = {1, 7, 3, 5};
|
||||
const uint8 EdgeFlags[] = {
|
||||
TunnelToNegativeY, TunnelToPositiveY,
|
||||
TunnelToNegativeX, TunnelToPositiveX};
|
||||
for (int32 EdgeIndex = 0; EdgeIndex < UE_ARRAY_COUNT(EdgeIndices); ++EdgeIndex)
|
||||
{
|
||||
if ((Centre.OpenTunnelEdges & EdgeFlags[EdgeIndex]) != 0)
|
||||
{
|
||||
Macro = SmoothMaximum(Macro, GeneratedTunnelEdge(
|
||||
Plan, Centre, *ColumnContext->Cells[EdgeIndices[EdgeIndex]], Z,
|
||||
ColumnContext->TunnelT[EdgeIndex],
|
||||
ColumnContext->TunnelHorizontal[EdgeIndex]), 5.0f);
|
||||
}
|
||||
}
|
||||
|
||||
for (int32 Index = 0; Index < UE_ARRAY_COUNT(ColumnContext->Cells); ++Index)
|
||||
{
|
||||
const FGeneratedCaveCell& Cell = *ColumnContext->Cells[Index];
|
||||
if ((Cell.FeatureFlags & HasVerticalShaftFlag) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const float DistanceSquared = ColumnContext->RoomDistanceSquared[Index];
|
||||
const float MaximumDistance = Plan.Generation.ShaftRadiusVoxels - Macro + 5.0f;
|
||||
if (MaximumDistance < 0.0f
|
||||
|| DistanceSquared > FMath::Square(MaximumDistance + 1.0e-3f))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const float DX = X - Cell.CenterX;
|
||||
const float DY = Y - Cell.CenterY;
|
||||
const float Horizontal = Plan.Generation.ShaftRadiusVoxels
|
||||
- FMath::Sqrt(DX * DX + DY * DY);
|
||||
- ColumnContext->RoomRadii[Index];
|
||||
const float Vertical = FMath::Min(Z - Bottom, Top - Z);
|
||||
Macro = SmoothMaximum(Macro,
|
||||
SmoothMinimum(Horizontal, Vertical, 5.0f), 5.0f);
|
||||
}
|
||||
|
||||
float DetailFade = 1.0f;
|
||||
for (const FLandingSite& Site : Plan.Landings)
|
||||
for (int32 SiteIndex = 0; SiteIndex < Plan.Landings.Num(); ++SiteIndex)
|
||||
{
|
||||
const float DX = X - Site.Position.X;
|
||||
const float DY = Y - Site.Position.Y;
|
||||
const float DistanceSquared = DX * DX + DY * DY;
|
||||
const float Distance = FMath::Sqrt(DistanceSquared);
|
||||
const FLandingSite& Site = Plan.Landings[SiteIndex];
|
||||
float DistanceSquared = 0.0f;
|
||||
float Distance = 0.0f;
|
||||
if (ColumnContext->bHasLandingXYCache)
|
||||
{
|
||||
DistanceSquared = ColumnContext->LandingDistanceSquared[SiteIndex];
|
||||
Distance = ColumnContext->LandingDistance[SiteIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
const float DX = X - Site.Position.X;
|
||||
const float DY = Y - Site.Position.Y;
|
||||
DistanceSquared = DX * DX + DY * DY;
|
||||
Distance = FMath::Sqrt(DistanceSquared);
|
||||
}
|
||||
const float RoomAir = FMath::Min(
|
||||
Site.PadRadius - Distance,
|
||||
FMath::Min(Z - Site.FloorZ, Site.FloorZ + MinimumCaveHeight - Z));
|
||||
@@ -1106,39 +1416,20 @@ namespace
|
||||
DetailFade = 0.0f;
|
||||
}
|
||||
|
||||
const float AnchorReach = Plan.Generation.RoomPitchVoxels * 1.25f
|
||||
+ FMath::Max(32.0f, Plan.Generation.TunnelRadiusVoxels);
|
||||
if (DistanceSquared > FMath::Square(AnchorReach))
|
||||
if (!Site.bGeneratedAnchorPrepared
|
||||
|| DistanceSquared > FMath::Square(Site.GeneratedAnchorReach))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const FIntPoint LandingCell = GenerationCellAt(Plan, Site.Position.X, Site.Position.Y);
|
||||
FGeneratedCaveCell NearestCell;
|
||||
float BestDistanceSq = FLT_MAX;
|
||||
for (int32 CellY = -1; CellY <= 1; ++CellY)
|
||||
for (int32 CellX = -1; CellX <= 1; ++CellX)
|
||||
{
|
||||
const FGeneratedCaveCell Candidate = BuildGeneratedCell(
|
||||
Plan, LandingCell.X + CellX, LandingCell.Y + CellY);
|
||||
const float CandidateDistanceSq = FMath::Square(Candidate.CenterX - Site.Position.X)
|
||||
+ FMath::Square(Candidate.CenterY - Site.Position.Y);
|
||||
if (CandidateDistanceSq < BestDistanceSq)
|
||||
{
|
||||
BestDistanceSq = CandidateDistanceSq;
|
||||
NearestCell = Candidate;
|
||||
}
|
||||
}
|
||||
const FVector AnchorA(Site.Position.X, Site.Position.Y, Site.FloorZ + 24.0f);
|
||||
const FVector AnchorB(NearestCell.CenterX, NearestCell.CenterY,
|
||||
NearestCell.FloorZ + Plan.Generation.TunnelHalfHeightVoxels);
|
||||
const FVector Segment = AnchorB - AnchorA;
|
||||
const float SegmentLengthSq = Segment.SizeSquared();
|
||||
const FVector Point(X, Y, Z);
|
||||
const float T = SegmentLengthSq > 1.0e-8f
|
||||
? FMath::Clamp(FVector::DotProduct(Point - AnchorA, Segment) / SegmentLengthSq,
|
||||
0.0f, 1.0f) : 0.0f;
|
||||
const float T = Site.GeneratedAnchorLengthSquared > 1.0e-8f
|
||||
? FMath::Clamp(FVector::DotProduct(
|
||||
Point - Site.GeneratedAnchorStart, Site.GeneratedAnchorDelta)
|
||||
/ Site.GeneratedAnchorLengthSquared, 0.0f, 1.0f) : 0.0f;
|
||||
const FVector AnchorDelta = Point
|
||||
- (Site.GeneratedAnchorStart + Site.GeneratedAnchorDelta * T);
|
||||
const float Anchor = FMath::Max(32.0f, Plan.Generation.TunnelRadiusVoxels)
|
||||
- (Point - (AnchorA + Segment * T)).Size();
|
||||
- AnchorDelta.Size();
|
||||
Macro = SmoothMaximum(Macro, Anchor, 5.0f);
|
||||
}
|
||||
|
||||
@@ -1152,9 +1443,13 @@ namespace
|
||||
continue;
|
||||
}
|
||||
const FLandingSite& Site = Plan.Landings[SiteIndex];
|
||||
const float Horizontal = LandingBridgeRadius - FMath::Sqrt(
|
||||
DistanceSquaredToSegmentXY(X, Y, Site.Position, Hub.Position));
|
||||
const float T = SegmentParameterXY(X, Y, Site.Position, Hub.Position);
|
||||
const float Horizontal = ColumnContext->bHasLandingXYCache
|
||||
? ColumnContext->LandingBridgeHorizontal[SiteIndex]
|
||||
: LandingBridgeRadius - FMath::Sqrt(
|
||||
DistanceSquaredToSegmentXY(X, Y, Site.Position, Hub.Position));
|
||||
const float T = ColumnContext->bHasLandingXYCache
|
||||
? ColumnContext->LandingBridgeT[SiteIndex]
|
||||
: SegmentParameterXY(X, Y, Site.Position, Hub.Position);
|
||||
const float FloorZ = FMath::Lerp(Site.FloorZ, Hub.FloorZ, T);
|
||||
const float Vertical = FMath::Min(
|
||||
Z - FloorZ, FloorZ + MinimumCaveHeight - Z);
|
||||
@@ -1455,6 +1750,10 @@ void ResetThreadCache()
|
||||
{
|
||||
GPlanCache = FThreadPlanCache();
|
||||
GShaftEdgeCache = FThreadShaftEdgeCache();
|
||||
GParameterizedRoomCellCache.Reset();
|
||||
GParameterizedRoomCellCacheMin = FIntPoint::ZeroValue;
|
||||
GParameterizedRoomCellCacheSize = FIntPoint::ZeroValue;
|
||||
GParameterizedRoomCellCachePlan = nullptr;
|
||||
}
|
||||
|
||||
static float EvaluateInternal(const UVoxelGenerator& Generator,
|
||||
@@ -1462,7 +1761,8 @@ static float EvaluateInternal(const UVoxelGenerator& Generator,
|
||||
float* OutPreEditDensity, float DetailScale,
|
||||
const FBrickEvaluationContext* Context = nullptr,
|
||||
bool bParameterized = false,
|
||||
const FParameterizedBrickContext* ParameterizedContext = nullptr)
|
||||
const FParameterizedBrickContext* ParameterizedContext = nullptr,
|
||||
const FParameterizedColumnContext* ParameterizedColumnContext = nullptr)
|
||||
{
|
||||
if (Context == nullptr && ParameterizedContext == nullptr)
|
||||
{
|
||||
@@ -1509,7 +1809,8 @@ static float EvaluateInternal(const UVoxelGenerator& Generator,
|
||||
{
|
||||
float DetailFade = 1.0f;
|
||||
const float Macro = bParameterized
|
||||
? EvaluateParameterizedMacro(*Plan, WorldX, WorldY, WorldZ, &DetailFade)
|
||||
? EvaluateParameterizedMacro(*Plan, WorldX, WorldY, WorldZ, &DetailFade,
|
||||
ParameterizedContext, ParameterizedColumnContext)
|
||||
: (Context != nullptr && Context->bMacroXYPrecomputed
|
||||
? EvaluateMacroFromBrick(*Plan, *Context, WorldX, WorldY, WorldZ, &DetailFade)
|
||||
: EvaluateMacro(*Plan, WorldX, WorldY, WorldZ, &DetailFade));
|
||||
@@ -1635,6 +1936,51 @@ float EvaluateParameterizedWithDetailValue(const UVoxelGenerator& Generator,
|
||||
OutPreEditDensity, DetailScale, nullptr, true, Context);
|
||||
}
|
||||
|
||||
void EvaluateParameterizedColumnWithDetailValues(
|
||||
const UVoxelGenerator& Generator, float WorldX, float WorldY, float WorldZStart,
|
||||
const float* DetailValues, int32 DetailStride, int32 Count, float* OutValues,
|
||||
const FParameterizedBrickContext* Context, float DetailScale)
|
||||
{
|
||||
if (DetailValues == nullptr || OutValues == nullptr || Count <= 0 || DetailStride <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const FIntVector Chunk(
|
||||
FMath::FloorToInt(WorldX / CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldY / CHUNK_SIZE),
|
||||
FMath::FloorToInt(WorldZStart / CHUNK_SIZE));
|
||||
const FStratePlan* Plan = Context != nullptr && Context->bPlanResolved
|
||||
? static_cast<const FStratePlan*>(Context->PlanToken) : nullptr;
|
||||
if (Plan == nullptr)
|
||||
{
|
||||
EnsurePlan(Generator);
|
||||
Plan = FindStrate(Chunk);
|
||||
}
|
||||
const int32 EndChunkZ = FMath::FloorToInt(
|
||||
(WorldZStart + static_cast<float>(Count - 1)) / CHUNK_SIZE);
|
||||
if (Plan == nullptr || Chunk.Z != EndChunkZ)
|
||||
{
|
||||
for (int32 Z = 0; Z < Count; ++Z)
|
||||
{
|
||||
OutValues[Z] = EvaluateInternal(
|
||||
Generator, WorldX, WorldY, WorldZStart + static_cast<float>(Z),
|
||||
DetailValues[Z * DetailStride], true, nullptr, DetailScale,
|
||||
nullptr, true, Context);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
FParameterizedColumnContext ColumnContext;
|
||||
BuildParameterizedColumnContext(*Plan, WorldX, WorldY, Context, ColumnContext);
|
||||
for (int32 Z = 0; Z < Count; ++Z)
|
||||
{
|
||||
OutValues[Z] = EvaluateInternal(
|
||||
Generator, WorldX, WorldY, WorldZStart + static_cast<float>(Z),
|
||||
DetailValues[Z * DetailStride], true, nullptr, DetailScale,
|
||||
nullptr, true, Context, &ColumnContext);
|
||||
}
|
||||
}
|
||||
|
||||
void BuildParameterizedBrickContext(
|
||||
const UVoxelGenerator& Generator, FIntVector OriginVoxels, FIntVector Size,
|
||||
FParameterizedBrickContext& OutContext)
|
||||
@@ -1674,6 +2020,58 @@ void BuildParameterizedBrickContext(
|
||||
static_cast<float>(OriginVoxels.Z + Size.Z));
|
||||
FindPassageCandidates(FBox(Min, Max), OutContext.PassageCandidates);
|
||||
OutContext.bMayContainPassage = !OutContext.PassageCandidates.IsEmpty();
|
||||
|
||||
if (Plan != nullptr && bStableStrate && !OutContext.bIsGapChunk
|
||||
&& Size.X > 0 && Size.Y > 0)
|
||||
{
|
||||
FIntPoint MinimumBaseCell(MAX_int32, MAX_int32);
|
||||
FIntPoint MaximumBaseCell(MIN_int32, MIN_int32);
|
||||
const int32 CornerX[] = {OriginVoxels.X, End.X};
|
||||
const int32 CornerY[] = {OriginVoxels.Y, End.Y};
|
||||
for (const int32 WorldY : CornerY)
|
||||
for (const int32 WorldX : CornerX)
|
||||
{
|
||||
const FIntPoint Cell = GenerationCellAt(
|
||||
*Plan, static_cast<float>(WorldX), static_cast<float>(WorldY));
|
||||
MinimumBaseCell.X = FMath::Min(MinimumBaseCell.X, Cell.X);
|
||||
MinimumBaseCell.Y = FMath::Min(MinimumBaseCell.Y, Cell.Y);
|
||||
MaximumBaseCell.X = FMath::Max(MaximumBaseCell.X, Cell.X);
|
||||
MaximumBaseCell.Y = FMath::Max(MaximumBaseCell.Y, Cell.Y);
|
||||
}
|
||||
|
||||
const FIntPoint NeededMin = MinimumBaseCell - FIntPoint(1, 1);
|
||||
const FIntPoint NeededMax = MaximumBaseCell + FIntPoint(1, 1);
|
||||
const bool bCacheContainsNeededCells = GParameterizedRoomCellCachePlan == Plan
|
||||
&& GParameterizedRoomCellCacheMin.X <= NeededMin.X
|
||||
&& GParameterizedRoomCellCacheMin.Y <= NeededMin.Y
|
||||
&& GParameterizedRoomCellCacheMin.X + GParameterizedRoomCellCacheSize.X - 1
|
||||
>= NeededMax.X
|
||||
&& GParameterizedRoomCellCacheMin.Y + GParameterizedRoomCellCacheSize.Y - 1
|
||||
>= NeededMax.Y;
|
||||
if (!bCacheContainsNeededCells)
|
||||
{
|
||||
// Keep one extra generated-cell layer beyond the 3x3 query halo so adjacent
|
||||
// proof bricks reuse their deterministic room and edge rolls.
|
||||
GParameterizedRoomCellCacheMin = MinimumBaseCell - FIntPoint(2, 2);
|
||||
GParameterizedRoomCellCacheSize = MaximumBaseCell - MinimumBaseCell
|
||||
+ FIntPoint(5, 5);
|
||||
const int32 RoomCellCount = GParameterizedRoomCellCacheSize.X
|
||||
* GParameterizedRoomCellCacheSize.Y;
|
||||
GParameterizedRoomCellCache.SetNumUninitialized(RoomCellCount);
|
||||
int32 RoomCellIndex = 0;
|
||||
for (int32 CellY = 0; CellY < GParameterizedRoomCellCacheSize.Y; ++CellY)
|
||||
for (int32 CellX = 0; CellX < GParameterizedRoomCellCacheSize.X; ++CellX)
|
||||
{
|
||||
GParameterizedRoomCellCache[RoomCellIndex++] = BuildGeneratedCell(
|
||||
*Plan, GParameterizedRoomCellCacheMin.X + CellX,
|
||||
GParameterizedRoomCellCacheMin.Y + CellY);
|
||||
}
|
||||
GParameterizedRoomCellCachePlan = Plan;
|
||||
}
|
||||
OutContext.RoomCellCacheMin = GParameterizedRoomCellCacheMin;
|
||||
OutContext.RoomCellCacheSize = GParameterizedRoomCellCacheSize;
|
||||
OutContext.RoomCellCacheToken = GParameterizedRoomCellCache.GetData();
|
||||
}
|
||||
}
|
||||
|
||||
void BuildDetailBrick(const UVoxelGenerator& Generator, FIntVector OriginVoxels,
|
||||
@@ -1879,22 +2277,47 @@ void BuildDetailBrick(const UVoxelGenerator& Generator, FIntVector OriginVoxels,
|
||||
MinCellX + X, MinCellY + Y, MinCellZ + Z, BandSeed);
|
||||
}
|
||||
|
||||
for (int32 Z = 0; Z < Size.Z; ++Z)
|
||||
for (int32 Y = 0; Y < Size.Y; ++Y)
|
||||
int32 CellX[8];
|
||||
int32 CellY[8];
|
||||
int32 CellZ[8];
|
||||
float FadeX[8];
|
||||
float FadeY[8];
|
||||
float FadeZ[8];
|
||||
for (int32 X = 0; X < Size.X; ++X)
|
||||
{
|
||||
const float SX = static_cast<float>(OriginVoxels.X + X)
|
||||
/ static_cast<float>(CellSize);
|
||||
const int32 Cell = FMath::FloorToInt(SX);
|
||||
CellX[X] = Cell - MinCellX;
|
||||
FadeX[X] = Fade(SX - static_cast<float>(Cell));
|
||||
}
|
||||
for (int32 Y = 0; Y < Size.Y; ++Y)
|
||||
{
|
||||
const float SY = static_cast<float>(OriginVoxels.Y + Y)
|
||||
/ static_cast<float>(CellSize);
|
||||
const int32 Cell = FMath::FloorToInt(SY);
|
||||
CellY[Y] = Cell - MinCellY;
|
||||
FadeY[Y] = Fade(SY - static_cast<float>(Cell));
|
||||
}
|
||||
for (int32 Z = 0; Z < Size.Z; ++Z)
|
||||
{
|
||||
const float SZ = static_cast<float>(OriginVoxels.Z + Z)
|
||||
/ static_cast<float>(CellSize);
|
||||
const int32 IX = FMath::FloorToInt(SX) - MinCellX;
|
||||
const int32 IY = FMath::FloorToInt(SY) - MinCellY;
|
||||
const int32 IZ = FMath::FloorToInt(SZ) - MinCellZ;
|
||||
const float AX = Fade(SX - static_cast<float>(FMath::FloorToInt(SX)));
|
||||
const float AY = Fade(SY - static_cast<float>(FMath::FloorToInt(SY)));
|
||||
const float AZ = Fade(SZ - static_cast<float>(FMath::FloorToInt(SZ)));
|
||||
const int32 Cell = FMath::FloorToInt(SZ);
|
||||
CellZ[Z] = Cell - MinCellZ;
|
||||
FadeZ[Z] = Fade(SZ - static_cast<float>(Cell));
|
||||
}
|
||||
|
||||
for (int32 Z = 0; Z < Size.Z; ++Z)
|
||||
for (int32 Y = 0; Y < Size.Y; ++Y)
|
||||
for (int32 X = 0; X < Size.X; ++X)
|
||||
{
|
||||
const int32 IX = CellX[X];
|
||||
const int32 IY = CellY[Y];
|
||||
const int32 IZ = CellZ[Z];
|
||||
const float AX = FadeX[X];
|
||||
const float AY = FadeY[Y];
|
||||
const float AZ = FadeZ[Z];
|
||||
const int32 Index = (IZ * GridY + IY) * GridX + IX;
|
||||
const float Value = CellSize == 1
|
||||
? Grid[Index]
|
||||
|
||||
@@ -66,6 +66,7 @@ namespace VoxelForgeRedefinedDensityField
|
||||
struct FParameterizedBrickContext
|
||||
{
|
||||
const void* PlanToken = nullptr;
|
||||
const void* RoomCellCacheToken = nullptr;
|
||||
bool bPlanResolved = false;
|
||||
bool bChunkResolved = false;
|
||||
bool bIsGapChunk = false;
|
||||
@@ -74,6 +75,8 @@ namespace VoxelForgeRedefinedDensityField
|
||||
FIntVector BrickOrigin = FIntVector::ZeroValue;
|
||||
FIntVector BrickSize = FIntVector::ZeroValue;
|
||||
FIntVector Chunk = FIntVector::ZeroValue;
|
||||
FIntPoint RoomCellCacheMin = FIntPoint::ZeroValue;
|
||||
FIntPoint RoomCellCacheSize = FIntPoint::ZeroValue;
|
||||
TArray<int32> PassageCandidates;
|
||||
};
|
||||
|
||||
@@ -117,6 +120,12 @@ namespace VoxelForgeRedefinedDensityField
|
||||
float DetailValue, float* OutPreEditDensity = nullptr, float DetailScale = 1.0f,
|
||||
const FParameterizedBrickContext* Context = nullptr);
|
||||
|
||||
/** Evaluate a Z column while reusing its XY-dependent room graph data. */
|
||||
VOXELFORGE_API void EvaluateParameterizedColumnWithDetailValues(
|
||||
const UVoxelGenerator& Generator, float WorldX, float WorldY, float WorldZStart,
|
||||
const float* DetailValues, int32 DetailStride, int32 Count, float* OutValues,
|
||||
const FParameterizedBrickContext* Context = nullptr, float DetailScale = 1.0f);
|
||||
|
||||
VOXELFORGE_API bool TryClassifyParameterizedBrick(
|
||||
const UVoxelGenerator& Generator, FIntVector OriginVoxels, FIntVector Size,
|
||||
bool& bOutAir, float DetailScale = 1.0f,
|
||||
|
||||
Reference in new issue
Block a user