feat(gen): Jahni's playtest corrections -- rooms flattened, roads deleted
From walking a TunnelNetwork preset at (0,0). Four corrections, and two of
them were the same mistake.
2a. The (0,0) landing room is sized to host a FUTURE straight vertical
shaft from the surface, with flooring around the origin itself. The
room is the landing for a shaft the player digs later.
2b. The inter-strate descent no longer starts at the spine -- placed by
deterministic hash within a radius around (0,0). The spine room is for
the surface shaft; the descent is elsewhere.
2c. ⛔ DELETED the landing->hub connector roads from 413c15d. Jahni:
"its straight up making other tunnels straightly through everything...
they have nothing to do here." Zero references remain.
2d. ⭐ FLATTEN THE ROOM JOINS instead. Where two spherical rooms intersect
and leave a lip above head height, the join is brought down to a
walkable level.
⭐ 2c AND 2d ARE ONE CORRECTION. I solved "rooms do not connect" by
TUNNELLING BETWEEN THEM when the answer was MAKING THEIR FLOORS MEET. The
81-run sweep had already said so -- zero narrow-gap events, every failure a
vertical severance, renders showing "a sharp diagonal ledge descending into
black open space". A radial corridor to the origin could never have fixed
that; flattening the joins is aimed at exactly it.
2e. Descent tunnel 1.5x wider, per "1.5x bigger wouldn't be too much".
2f. Corridors WANDER. Jahni, from the renders: "its not a maze, its still a
latice made straight paths." The spanning tree changed WHICH edges
exist, not what an edge looks like -- every corridor was a straight
axis-aligned capsule, every junction 90 degrees. Now hash-jittered
chains, seam-safe like the tree.
RENDERER: 192x128 -> 512x288, and cameras are now seeded from the
player-fit set -- "a density-checked pose from the exact player-fit walk
mask". Previously they sat INSIDE ROCK looking at back-faces, which Jahni
spotted instantly and no metric could. First genuinely legible pictures of
this world.
ParallelFor landed at 6 sites in VoxelStrateMeasure before I stopped the
run; the rest of that work continues next.
⚠️ AND THE RENDERER IS ARCHITECTURALLY WRONG, per Jahni: "why, in engine,
does it take a minute to have clear view of thousands of angles, but in our
renderer it takes ages?" Because the engine MESHES ONCE and rasterises --
~32k density calls per chunk, then every angle is free. I RAYMARCH: tens of
millions of density calls PER IMAGE, nothing reused between frames. We
already have the mesher and an export mode. Next task renders from the
mesh.
Also from him: runs must stay under 30 minutes. This one hit 33 and
produced 2 images.
Build clean. Committed as a checkpoint -- the task was stopped mid-Part-3,
not finished.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
This commit is contained in:
1 parent
55bf807f60
commit
d97373c5a8
18 files changed
+2433
-1021
No files matched your search
@@ -269,9 +269,10 @@ static bool VF_IsAnyPassageFloorAt(
|
||||
continue;
|
||||
}
|
||||
const FVoxelPassage& Passage = Passages[PassageIndex];
|
||||
if (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|
||||
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
|
||||
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|
||||
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
|
||||
|| VF_IsWalkableTunnelFloor(Passage, Position))
|
||||
|| VF_IsWalkableTunnelFloor(Passage, Position)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -826,8 +827,6 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
int32 TotalPassages = 0;
|
||||
int32 NumAimedAtUpperPlayerFit = 0;
|
||||
int32 NumAimedAtLowerPlayerFit = 0;
|
||||
int32 NumUpperRootConnectors = 0;
|
||||
int32 NumLowerRootConnectors = 0;
|
||||
TSet<FString> NoQueryArchetypes;
|
||||
|
||||
const auto ArchetypeName = [](ECaveGeneratorType Archetype)
|
||||
@@ -915,39 +914,6 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
}
|
||||
};
|
||||
|
||||
const auto CommonRootFloorFor = [&](float StrateTopZ, float StrateBottomZ,
|
||||
float BoundarySealThickness) -> float
|
||||
{
|
||||
// The two mouth queries may legitimately return floors near opposite strate faces. The
|
||||
// root is the origin landing room's floor, not a hidden continuous shaft and not a
|
||||
// passage-order-dependent midpoint. Reusing the same top-anchored geometry here makes
|
||||
// every connector meet the (0,0) room at its actual support level.
|
||||
const VoxelPassageGeometry::FOriginLandingGeometry OriginLanding =
|
||||
VoxelPassageGeometry::BuildOriginLandingGeometry(
|
||||
StrateTopZ, StrateBottomZ, BoundarySealThickness, OriginSpineRadius);
|
||||
if (OriginLanding.bValid)
|
||||
{
|
||||
return OriginLanding.FloorZ;
|
||||
}
|
||||
|
||||
// Malformed tiny strates still fail closed. This fallback is only for the descriptor
|
||||
// builder's diagnostic path; normal authored strates always satisfy the room interval.
|
||||
const float Seal = FMath::Max(BoundarySealThickness, 0.0f);
|
||||
const float InnerBottom = StrateBottomZ + Seal;
|
||||
const float InnerTop = StrateTopZ - Seal;
|
||||
const float ConnectorHeight = FMath::Max(
|
||||
12.0f, FVoxelPlayerCapsuleConstants::HeightVoxels + 4.0f);
|
||||
constexpr float PassageBlendSafety = 4.0f;
|
||||
const float MinimumFloor = InnerBottom + 3.0f + PassageBlendSafety;
|
||||
const float MaximumFloor = InnerTop - ConnectorHeight - PassageBlendSafety;
|
||||
if (FMath::IsFinite(MinimumFloor) && FMath::IsFinite(MaximumFloor)
|
||||
&& MaximumFloor >= MinimumFloor)
|
||||
{
|
||||
return 0.5f * (MinimumFloor + MaximumFloor);
|
||||
}
|
||||
return 0.5f * (InnerBottom + InnerTop);
|
||||
};
|
||||
|
||||
//=========================================================================
|
||||
// INTER-STRATE PASSAGES: tunnels connecting consecutive strates.
|
||||
// Each passage is randomly assigned one of 5 types, which determines
|
||||
@@ -974,6 +940,17 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
|
||||
const float DistLo = FMath::Min(Cfg.DistanceMin, Cfg.DistanceMax);
|
||||
const float DistHi = FMath::Max(Cfg.DistanceMin, Cfg.DistanceMax);
|
||||
// The origin room owns the future straight shaft. Keep the progression mouth outside its
|
||||
// carve/blend envelope, then choose a deterministic point in the authored annulus. If an
|
||||
// asset has an invalid inner radius, repair only that radius; the outer authored radius is
|
||||
// preserved unless it is too small to contain the required inner edge.
|
||||
const float OriginLandingHalfWidth =
|
||||
VoxelPassageGeometry::LandingHalfWidthForRadius(OriginSpineRadius);
|
||||
const float MinimumDescentDistance = FMath::Max(
|
||||
OriginLandingHalfWidth + VoxelPassageGeometry::LandingCarveBlendVoxels,
|
||||
VoxelPassageGeometry::MinimumTurnFloorWidthVoxels);
|
||||
const float PlacementLo = FMath::Max(DistLo, MinimumDescentDistance);
|
||||
const float PlacementHi = FMath::Max(DistHi, PlacementLo);
|
||||
|
||||
const int32 Conns = FMath::Max(0, Cfg.Connections);
|
||||
for (int32 c = 0; c < Conns; c++)
|
||||
@@ -1010,9 +987,11 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
break;
|
||||
}
|
||||
|
||||
// PLACEMENT: random angle, distance from the (0,0) spine within config range.
|
||||
// PLACEMENT: random angle, distance from the (0,0) spine within a deterministic
|
||||
// annulus. It is deliberately not the spine: that room is reserved for the future
|
||||
// surface shaft, while this passage opens a separate room in the strate network.
|
||||
const float Angle = PassageRandom01(PassageSaltAngle) * (2.0f * PI);
|
||||
const float Distance = PassageRandomRange(DistLo, DistHi, PassageSaltDistance);
|
||||
const float Distance = PassageRandomRange(PlacementLo, PlacementHi, PassageSaltDistance);
|
||||
const float PX = FMath::Cos(Angle) * Distance;
|
||||
const float PY = FMath::Sin(Angle) * Distance;
|
||||
|
||||
@@ -1049,7 +1028,8 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
PX,
|
||||
PY,
|
||||
MaxLateralSnapFor(*UpperDef),
|
||||
SuggestedUpperPoint);
|
||||
SuggestedUpperPoint,
|
||||
CachedSeed);
|
||||
|
||||
if (bAimedUpperAtPlayerFitPoint)
|
||||
{
|
||||
@@ -1088,7 +1068,8 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
PX,
|
||||
PY,
|
||||
MaxLateralSnap,
|
||||
SuggestedLowerPoint);
|
||||
SuggestedLowerPoint,
|
||||
CachedSeed);
|
||||
|
||||
if (bAimedLowerAtPlayerFitPoint)
|
||||
{
|
||||
@@ -1289,10 +1270,9 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
: FVector::ZeroVector,
|
||||
PassageRandom01(PassageSaltLowerDoor) * 2.0f * PI);
|
||||
|
||||
// A source-fit answer proves only a local pose; none of the pure queries performs a
|
||||
// flood fill over the live strate. Every mouth therefore receives the same explicit
|
||||
// flat-floor connector to the deterministic root, including answered mouths. This
|
||||
// is what makes the join guaranteed rather than merely probable.
|
||||
// A source-fit answer is the landing itself. Keep it local to the requested room: the
|
||||
// (0,0) landing is reserved for the future straight shaft, so no radial connector is
|
||||
// synthesized here.
|
||||
Passage.UpperLanding = VF_BuildPassageLanding(
|
||||
FVector(UpperX, UpperY, TopZ),
|
||||
Cfg.MouthRadius,
|
||||
@@ -1300,13 +1280,7 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
UpperTopZ,
|
||||
UpperBottomZ,
|
||||
UpperDef ? BoundarySealThicknessFor(*UpperDef) : 0.0f,
|
||||
bAimedUpperAtPlayerFitPoint,
|
||||
/*bHasNetworkConnector=*/true,
|
||||
FVector(0.0f, 0.0f,
|
||||
CommonRootFloorFor(
|
||||
UpperTopZ, UpperBottomZ,
|
||||
UpperDef ? BoundarySealThicknessFor(*UpperDef) : 0.0f)),
|
||||
OriginSpineRadius);
|
||||
bAimedUpperAtPlayerFitPoint);
|
||||
Passage.LowerLanding = VF_BuildPassageLanding(
|
||||
FVector(LowerX, LowerY, BottomZ),
|
||||
Cfg.MouthRadius,
|
||||
@@ -1314,17 +1288,9 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
LowerTopZ,
|
||||
(float)(Lower.BottomChunkZ) * CHUNK_SIZE,
|
||||
LowerDef ? BoundarySealThicknessFor(*LowerDef) : 0.0f,
|
||||
bAimedLowerAtPlayerFitPoint,
|
||||
/*bHasNetworkConnector=*/true,
|
||||
FVector(0.0f, 0.0f,
|
||||
CommonRootFloorFor(
|
||||
LowerTopZ, (float)(Lower.BottomChunkZ) * CHUNK_SIZE,
|
||||
LowerDef ? BoundarySealThicknessFor(*LowerDef) : 0.0f)),
|
||||
OriginSpineRadius);
|
||||
bAimedLowerAtPlayerFitPoint);
|
||||
Passage.UpperPoint = Passage.UpperLanding.StandingPoint;
|
||||
Passage.LowerPoint = Passage.LowerLanding.StandingPoint;
|
||||
if (Passage.UpperLanding.bHasNetworkConnector) ++NumUpperRootConnectors;
|
||||
if (Passage.LowerLanding.bHasNetworkConnector) ++NumLowerRootConnectors;
|
||||
|
||||
if (Cfg.Style == EVoxelPassageStyle::Straight
|
||||
&& Passage.UpperLanding.HalfWidth > 0.0f
|
||||
@@ -1554,14 +1520,11 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
}
|
||||
// Fallback / bounds if an invalid authored width produced no positive profile.
|
||||
Passage.Radius = FMath::Max(Passage.Radius, FMath::Max(Cfg.MouthRadius, Cfg.MidRadius));
|
||||
Passage.Radius = FMath::Max(Passage.Radius,
|
||||
FMath::Max(Passage.UpperLanding.ConnectorRadius,
|
||||
Passage.LowerLanding.ConnectorRadius));
|
||||
|
||||
// Bounding sphere over the tube, both rooms, both floor slabs, and both optional root
|
||||
// connectors (+ widest radius + blend) for culling. Under-sizing this sphere would
|
||||
// cull a real landing and leave a sealed pocket, so the room's full box diagonal is
|
||||
// included rather than treating the standing anchor as a point.
|
||||
// Bounding sphere over the tube, both rooms, and both floor slabs (+ widest radius +
|
||||
// blend) for culling. Under-sizing this sphere would cull a real landing and leave a
|
||||
// sealed pocket, so the room's full box diagonal is included rather than treating the
|
||||
// standing anchor as a point.
|
||||
{
|
||||
FVector BoundsMin(FLT_MAX, FLT_MAX, FLT_MAX);
|
||||
FVector BoundsMax(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
||||
@@ -1590,31 +1553,6 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
+ 0.25f * FMath::Square(Height))
|
||||
+ Landing.FloorThickness + 4.0f;
|
||||
IncludePoint(RoomCenter, RoomRadius);
|
||||
if (Landing.bHasNetworkConnector)
|
||||
{
|
||||
const float HubRadius = Landing.RootSpineRadius
|
||||
+ Landing.ConnectorRadius;
|
||||
const float HubHalfHeight = 0.5f * FMath::Max(
|
||||
Landing.RootCeilingZ - Landing.RootFloorZ, 0.0f);
|
||||
IncludePoint(FVector(0.0f, 0.0f,
|
||||
(Landing.RootFloorZ + Landing.RootCeilingZ) * 0.5f),
|
||||
FMath::Sqrt(FMath::Square(HubRadius)
|
||||
+ FMath::Square(HubHalfHeight))
|
||||
+ Landing.FloorThickness + 4.0f);
|
||||
const float ConnectorHalfHeight = FMath::Max(
|
||||
0.5f * FMath::Max(
|
||||
Landing.ConnectorCeilingZ - Landing.FloorZ, 0.0f),
|
||||
Landing.ConnectorRadius);
|
||||
IncludePoint(Landing.ConnectorStart,
|
||||
ConnectorHalfHeight + 4.0f);
|
||||
if (Landing.bHasConnectorBend)
|
||||
{
|
||||
IncludePoint(Landing.ConnectorControl,
|
||||
ConnectorHalfHeight + 4.0f);
|
||||
}
|
||||
IncludePoint(Landing.ConnectorEnd,
|
||||
ConnectorHalfHeight + 4.0f);
|
||||
}
|
||||
};
|
||||
IncludeLanding(Passage.UpperLanding);
|
||||
IncludeLanding(Passage.LowerLanding);
|
||||
@@ -1646,13 +1584,11 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log,
|
||||
TEXT("[StrateManager] Passage landings: upper %d/%d and lower %d/%d source-fit; root floor connectors %d/%d; %d/%d mouth queries fell back to random reach (archetypes with no query: %s)."),
|
||||
TEXT("[StrateManager] Passage landings: upper %d/%d and lower %d/%d source-fit; %d/%d mouth queries fell back to random reach (archetypes with no query: %s)."),
|
||||
NumAimedAtUpperPlayerFit,
|
||||
TotalPassages,
|
||||
NumAimedAtLowerPlayerFit,
|
||||
TotalPassages,
|
||||
NumUpperRootConnectors,
|
||||
NumLowerRootConnectors,
|
||||
(TotalPassages * 2) - NumAimedAtUpperPlayerFit - NumAimedAtLowerPlayerFit,
|
||||
TotalPassages * 2,
|
||||
*NoQueryList);
|
||||
@@ -1686,7 +1622,9 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
// Keep the legacy type: this is the explicitly opted-in above-ground vertical opening,
|
||||
// not the default inter-strate descent style.
|
||||
Entry.PassageType = EVoxelPassageType::VerticalShaft;
|
||||
Entry.Radius = FMath::Max(OriginSpineRadius * 0.7f, 4.0f);
|
||||
// Match the reserved future shaft envelope exactly. The entry is only opened through
|
||||
// the top seal into the finite landing room; lower strate seals remain untouched.
|
||||
Entry.Radius = FMath::Max(OriginSpineRadius, 1.0f);
|
||||
Entry.UpperPoint = FVector(0.0f, 0.0f, TopZ + CHUNK_SIZE);
|
||||
// The capsule's lower tangent meets the room ceiling; it never bores through the
|
||||
// room's floor or creates an origin column in strate 0.
|
||||
@@ -1779,8 +1717,8 @@ float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float
|
||||
}
|
||||
|
||||
// A landing is a real room with a hard flat-floor half-space, not a sphere around the
|
||||
// tube endpoint. The optional connector is the same fixed SDF geometry selected during
|
||||
// GeneratePassages; it never performs a source query here.
|
||||
// tube endpoint. It is the same fixed SDF geometry selected during GeneratePassages; it
|
||||
// never performs a source query here and does not synthesize a radial root connector.
|
||||
const float UpperLandingSDF = VF_EvaluatePassageLandingSDF(Pos, P.UpperLanding);
|
||||
const float LowerLandingSDF = VF_EvaluatePassageLandingSDF(Pos, P.LowerLanding);
|
||||
MinSDF = VoxelSDF::SmoothMin(MinSDF, UpperLandingSDF, BlendK);
|
||||
@@ -1807,9 +1745,10 @@ void UVoxelStrateManager::ApplyPassageModifier(
|
||||
for (const int32 PassageIndex : Nearby)
|
||||
{
|
||||
const FVoxelPassage& Passage = Passages[PassageIndex];
|
||||
if (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|
||||
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
|
||||
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|
||||
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
|
||||
|| VF_IsWalkableTunnelFloor(Passage, Position))
|
||||
|| VF_IsWalkableTunnelFloor(Passage, Position)))
|
||||
{
|
||||
// This is the one bidirectional part of PassageCarveOp: a floor is a proved solid
|
||||
// support slab. It is deliberately applied after the air carve so a tube can never
|
||||
@@ -1928,9 +1867,10 @@ void UVoxelStrateManager::ApplyPassageLandingFloorMC(
|
||||
for (const int32 PassageIndex : Nearby)
|
||||
{
|
||||
const FVoxelPassage& Passage = Passages[PassageIndex];
|
||||
if (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|
||||
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
|
||||
|| VF_IsWalkableTunnelFloor(Passage, Position))
|
||||
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
|
||||
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|
||||
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
|
||||
|| VF_IsWalkableTunnelFloor(Passage, Position)))
|
||||
{
|
||||
// Result is in MC convention here (negative = solid). This reassertion is the
|
||||
// structural floor backstop after the optional MC-space disturbance layer.
|
||||
@@ -1971,8 +1911,9 @@ void UVoxelStrateManager::ApplyPassageLandingRoomFloorMC(
|
||||
for (const int32 PassageIndex : Nearby)
|
||||
{
|
||||
const FVoxelPassage& Passage = Passages[PassageIndex];
|
||||
if (VF_IsPassageRoomFloor(Position, Passage.UpperLanding)
|
||||
|| VF_IsPassageRoomFloor(Position, Passage.LowerLanding))
|
||||
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
|
||||
&& (VF_IsPassageRoomFloor(Position, Passage.UpperLanding)
|
||||
|| VF_IsPassageRoomFloor(Position, Passage.LowerLanding)))
|
||||
{
|
||||
Density = FMath::Min(Density, -BaseDensity);
|
||||
break;
|
||||
@@ -2030,50 +1971,6 @@ bool UVoxelStrateManager::AnyPassageLandingFloorNearBox(
|
||||
return true;
|
||||
}
|
||||
|
||||
// The fallback connector carries the same support slab away from the room. Its
|
||||
// segment is not covered by the room AABB above; omitting it would let a tile that
|
||||
// contains only the connector floor prove AllAir and erase the guaranteed join.
|
||||
if (Landing->bHasNetworkConnector && Landing->ConnectorRadius > 0.0f
|
||||
&& FMath::IsFinite(Landing->ConnectorStart.X)
|
||||
&& FMath::IsFinite(Landing->ConnectorStart.Y)
|
||||
&& FMath::IsFinite(Landing->ConnectorStart.Z)
|
||||
&& FMath::IsFinite(Landing->ConnectorEnd.X)
|
||||
&& FMath::IsFinite(Landing->ConnectorEnd.Y)
|
||||
&& FMath::IsFinite(Landing->ConnectorEnd.Z))
|
||||
{
|
||||
const float ConnectorPad = Landing->ConnectorRadius + Pad;
|
||||
float ConnectorMinX = FMath::Min(
|
||||
Landing->ConnectorStart.X, Landing->ConnectorEnd.X) - ConnectorPad;
|
||||
float ConnectorMaxX = FMath::Max(
|
||||
Landing->ConnectorStart.X, Landing->ConnectorEnd.X) + ConnectorPad;
|
||||
float ConnectorMinY = FMath::Min(
|
||||
Landing->ConnectorStart.Y, Landing->ConnectorEnd.Y) - ConnectorPad;
|
||||
float ConnectorMaxY = FMath::Max(
|
||||
Landing->ConnectorStart.Y, Landing->ConnectorEnd.Y) + ConnectorPad;
|
||||
if (Landing->bHasConnectorBend)
|
||||
{
|
||||
ConnectorMinX = FMath::Min(
|
||||
ConnectorMinX, Landing->ConnectorControl.X - ConnectorPad);
|
||||
ConnectorMaxX = FMath::Max(
|
||||
ConnectorMaxX, Landing->ConnectorControl.X + ConnectorPad);
|
||||
ConnectorMinY = FMath::Min(
|
||||
ConnectorMinY, Landing->ConnectorControl.Y - ConnectorPad);
|
||||
ConnectorMaxY = FMath::Max(
|
||||
ConnectorMaxY, Landing->ConnectorControl.Y + ConnectorPad);
|
||||
}
|
||||
const float ConnectorMinZ = FMath::Min(
|
||||
Landing->FloorZ, Landing->RootFloorZ)
|
||||
- Landing->FloorThickness - Pad;
|
||||
const float ConnectorMaxZ = FMath::Max(
|
||||
Landing->FloorZ, Landing->RootFloorZ) + Pad;
|
||||
if (ConnectorMaxX >= MinVoxel.X && ConnectorMinX <= MaxVoxel.X
|
||||
&& ConnectorMaxY >= MinVoxel.Y && ConnectorMinY <= MaxVoxel.Y
|
||||
&& ConnectorMaxZ >= MinVoxel.Z && ConnectorMinZ <= MaxVoxel.Z)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// The default tube also owns a three-voxel support band. Its conservative segment AABB
|
||||
|
||||
Reference in new issue
Block a user