wip(assembler): M1 round 6 -- owner walk fixes: 25 cm geometry scale, one height datum, carve/fill sign, faster relief meshing
Owner saw content floating above native terrain, spiky triangles, reversed carve/fill and late relief. Causes: shared mesher placed Core tiles on a 12.5 cm grid; content/atmosphere asked strate ranges in 12.5 cm voxels; Core edits add density where the legacy diff subtracts. Horizon LOD8 bridge edge 5.657 -> 2.265 cells. Relief mesh p95 60.8 -> 42.0 ms (LOD0). New BuildHost checks for scale, height, blueprint carve/fill and per-kind streaming lag. 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
f67b519e75
commit
61633cb492
18 files changed
+1708
-142
No files matched your search
@@ -6,6 +6,7 @@
|
||||
#include "VoxelBiomeDefinition.h"
|
||||
#include "VoxelGenerator.h"
|
||||
#include "VoxelTypes.h" // CHUNK_SIZE / VOXEL_SIZE
|
||||
#include "VoxelWorld.h"
|
||||
#include "Components/ExponentialHeightFogComponent.h"
|
||||
#include "Components/SkyLightComponent.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
@@ -50,9 +51,12 @@ void UVoxelAtmosphereManager::UpdateForPlayer(const FVector& PlayerWorldPos)
|
||||
// identity".
|
||||
const FTransform Xf = O->GetActorTransform();
|
||||
const FVector LocalPos = Xf.InverseTransformPosition(PlayerWorldPos);
|
||||
const AVoxelWorld* VoxelWorld = Cast<AVoxelWorld>(O);
|
||||
const float ManagerLocalZ = VoxelWorld
|
||||
? VoxelWorld->ToStrateManagerLocalZ(LocalPos.Z) : LocalPos.Z;
|
||||
|
||||
const int32 Idx = StrateManager->GetStrateIndex(LocalPos.Z);
|
||||
const UVoxelStrateDefinition* Def = StrateManager->GetStrateAt(LocalPos.Z);
|
||||
const int32 Idx = StrateManager->GetStrateIndex(ManagerLocalZ);
|
||||
const UVoxelStrateDefinition* Def = StrateManager->GetStrateAt(ManagerLocalZ);
|
||||
|
||||
// Player's dominant biome (XY field) — atmosphere can vary by biome within a strate.
|
||||
// Requete en espace ACTEUR (voir LocalPos ci-dessus) : cm locaux -> voxels.
|
||||
@@ -60,7 +64,7 @@ void UVoxelAtmosphereManager::UpdateForPlayer(const FVector& PlayerWorldPos)
|
||||
const UVoxelBiomeDefinition* Biome = nullptr;
|
||||
if (Generator && Def)
|
||||
{
|
||||
const int32 ChunkZ = FMath::FloorToInt((LocalPos.Z / VOXEL_SIZE) / CHUNK_SIZE);
|
||||
const int32 ChunkZ = FMath::FloorToInt((ManagerLocalZ / VOXEL_SIZE) / CHUNK_SIZE);
|
||||
Biome = Generator->GetDominantBiomeAt(LocalPos.X / VOXEL_SIZE,
|
||||
LocalPos.Y / VOXEL_SIZE, ChunkZ);
|
||||
}
|
||||
@@ -91,18 +95,24 @@ void UVoxelAtmosphereManager::UpdateForPlayer(const FVector& PlayerWorldPos)
|
||||
if (Def)
|
||||
{
|
||||
float TopZ, BottomZ;
|
||||
if (StrateManager->GetStrateUnrealZRange(LocalPos.Z, TopZ, BottomZ))
|
||||
if (StrateManager->GetStrateUnrealZRange(ManagerLocalZ, TopZ, BottomZ))
|
||||
{
|
||||
const float LocalTopZ = VoxelWorld
|
||||
? VoxelWorld->FromStrateManagerLocalZ(TopZ) : TopZ;
|
||||
const float LocalBottomZ = VoxelWorld
|
||||
? VoxelWorld->FromStrateManagerLocalZ(BottomZ) : BottomZ;
|
||||
if (CeilingActor)
|
||||
{
|
||||
CeilingActor->SetActorLocationAndRotation(
|
||||
Xf.TransformPosition(FVector(LocalPos.X, LocalPos.Y, TopZ + Def->CeilingLayerZOffset)),
|
||||
Xf.TransformPosition(FVector(LocalPos.X, LocalPos.Y,
|
||||
LocalTopZ + Def->CeilingLayerZOffset)),
|
||||
Def->CeilingLayerRotation);
|
||||
}
|
||||
if (FloorActor)
|
||||
{
|
||||
FloorActor->SetActorLocationAndRotation(
|
||||
Xf.TransformPosition(FVector(LocalPos.X, LocalPos.Y, BottomZ + Def->FloorLayerZOffset)),
|
||||
Xf.TransformPosition(FVector(LocalPos.X, LocalPos.Y,
|
||||
LocalBottomZ + Def->FloorLayerZOffset)),
|
||||
Def->FloorLayerRotation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "VoxelBiomeDefinition.h"
|
||||
#include "VoxelGenerator.h"
|
||||
#include "VoxelSettings.h"
|
||||
#include "VoxelWorld.h"
|
||||
#include "VoxelCaveMorphology.h" // VoxelHash
|
||||
#include "Components/StaticMeshComponent.h"
|
||||
#include "Components/HierarchicalInstancedStaticMeshComponent.h"
|
||||
@@ -18,6 +19,114 @@
|
||||
#include "Tasks/Task.h"
|
||||
#include "HAL/PlatformProcess.h"
|
||||
#include "ProfilingDebugging/CpuProfilerTrace.h" // Unreal Insights scopes
|
||||
#include <VoxelForgeCore/VoxelForgeCore.h>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
struct FCoreSurfaceSnapshot
|
||||
{
|
||||
VoxelForgeCore::WorldAssemblyRecipeV1 Recipe;
|
||||
TMap<int32, VoxelForgeCore::WorldAssemblyStratumPlanV1> Plans;
|
||||
|
||||
const VoxelForgeCore::WorldAssemblyStratumPlanV1* FindPlan(int32 StrateIndex) const
|
||||
{
|
||||
return Plans.Find(StrateIndex);
|
||||
}
|
||||
|
||||
bool GetHeights(int32 StrateIndex, float LegacyVoxelX, float LegacyVoxelY,
|
||||
float& OutGroundLegacyVoxels, float& OutCapLegacyVoxels) const
|
||||
{
|
||||
const auto* Plan = FindPlan(StrateIndex);
|
||||
if (!Plan || !VoxelMath::IsFinite(LegacyVoxelX) || !VoxelMath::IsFinite(LegacyVoxelY))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// One 12.5 cm legacy voxel equals 128 subvoxels, half a 25 cm Core voxel.
|
||||
// Un voxel historique de 12,5 cm vaut 128 sous-voxels, soit la moitie d'un voxel Core de 25 cm.
|
||||
constexpr double LegacyVoxelToSubvoxels = 128.0;
|
||||
constexpr double SubvoxelsPerLegacyVoxel = LegacyVoxelToSubvoxels;
|
||||
constexpr double MaxInput = static_cast<double>(MAX_int64) / LegacyVoxelToSubvoxels;
|
||||
if (FMath::Abs(static_cast<double>(LegacyVoxelX)) > MaxInput
|
||||
|| FMath::Abs(static_cast<double>(LegacyVoxelY)) > MaxInput)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const VoxelForgeCore::Heightfield2D Heightfield(Recipe, *Plan);
|
||||
if (!Heightfield.isValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const int64 XSubvoxels = static_cast<int64>(std::llround(
|
||||
static_cast<double>(LegacyVoxelX) * LegacyVoxelToSubvoxels));
|
||||
const int64 YSubvoxels = static_cast<int64>(std::llround(
|
||||
static_cast<double>(LegacyVoxelY) * LegacyVoxelToSubvoxels));
|
||||
const auto Sample = Heightfield.point(
|
||||
XSubvoxels, YSubvoxels, 0);
|
||||
int64 SurfaceHeightSubvoxels = Sample.groundHeightSubvoxels;
|
||||
int64 SearchTopSubvoxels = Sample.groundHeightSubvoxels;
|
||||
for (const auto& Feature : Plan->features)
|
||||
{
|
||||
if ((Feature.type != 2 && Feature.type != 4)
|
||||
|| XSubvoxels < Feature.minimumSubvoxels[0] - 4 * 256
|
||||
|| XSubvoxels > Feature.maximumSubvoxels[0] + 4 * 256
|
||||
|| YSubvoxels < Feature.minimumSubvoxels[1] - 4 * 256
|
||||
|| YSubvoxels > Feature.maximumSubvoxels[1] + 4 * 256)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SearchTopSubvoxels = FMath::Max(
|
||||
SearchTopSubvoxels, Feature.maximumSubvoxels[2]);
|
||||
}
|
||||
SearchTopSubvoxels = FMath::Min(
|
||||
Sample.capHeightSubvoxels, SearchTopSubvoxels + 256);
|
||||
if (SearchTopSubvoxels > Sample.groundHeightSubvoxels)
|
||||
{
|
||||
// Quarter Core voxel, bounded to relevant features. / Quart de voxel Core, borne aux volumes pertinents.
|
||||
constexpr int64 ScanStepSubvoxels = 64;
|
||||
int64 PreviousZ = SearchTopSubvoxels;
|
||||
int64 PreviousDensity = Heightfield.point(XSubvoxels, YSubvoxels, PreviousZ).densitySubvoxels;
|
||||
for (int64 Z = SearchTopSubvoxels - ScanStepSubvoxels;
|
||||
Z >= Sample.groundHeightSubvoxels; Z -= ScanStepSubvoxels)
|
||||
{
|
||||
const int64 Density = Heightfield.point(XSubvoxels, YSubvoxels, Z).densitySubvoxels;
|
||||
if (PreviousDensity >= 0 && Density < 0)
|
||||
{
|
||||
const double Denominator = static_cast<double>(PreviousDensity - Density);
|
||||
const double Alpha = Denominator > 0.0
|
||||
? static_cast<double>(PreviousDensity) / Denominator : 0.5;
|
||||
SurfaceHeightSubvoxels = static_cast<int64>(std::llround(
|
||||
static_cast<double>(PreviousZ) + (static_cast<double>(Z - PreviousZ) * Alpha)));
|
||||
break;
|
||||
}
|
||||
PreviousZ = Z;
|
||||
PreviousDensity = Density;
|
||||
}
|
||||
}
|
||||
OutGroundLegacyVoxels = static_cast<float>(SurfaceHeightSubvoxels / SubvoxelsPerLegacyVoxel);
|
||||
OutCapLegacyVoxels = static_cast<float>(Sample.capHeightSubvoxels / SubvoxelsPerLegacyVoxel);
|
||||
return VoxelMath::IsFinite(OutGroundLegacyVoxels) && VoxelMath::IsFinite(OutCapLegacyVoxels);
|
||||
}
|
||||
|
||||
bool GetWater(int32 StrateIndex, bool& OutHasPlan, bool& OutEnabled, float& OutLocalCm) const
|
||||
{
|
||||
OutHasPlan = false;
|
||||
OutEnabled = false;
|
||||
OutLocalCm = -FLT_MAX;
|
||||
const auto* Plan = FindPlan(StrateIndex);
|
||||
if (!Plan)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
OutHasPlan = true;
|
||||
OutEnabled = Plan->waterEnabled;
|
||||
if (OutEnabled)
|
||||
{
|
||||
OutLocalCm = static_cast<float>(Plan->waterSurfaceSubvoxels) * (25.0f / 256.0f);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Global safety cap on spawned decoration ACTORS per cell (across all entries).
|
||||
// HISM instances are exempt — they're batched render data, capped per entry by MaxPerChunk.
|
||||
@@ -64,6 +173,37 @@ void UVoxelContentManager::Initialize(AActor* InOwner, UVoxelStrateManager* InSt
|
||||
Generator = InGenerator;
|
||||
Settings = InSettings;
|
||||
Seed = InSeed;
|
||||
NativeSurfaceSnapshot.Reset();
|
||||
|
||||
if (Settings)
|
||||
{
|
||||
FString ManifestJson, ManifestHash, ManifestError;
|
||||
if (Settings->GetWorldAssemblyManifestJson(ManifestJson, ManifestHash, ManifestError)
|
||||
&& !ManifestJson.IsEmpty())
|
||||
{
|
||||
FTCHARToUTF8 Utf8Json(*ManifestJson);
|
||||
std::string CoreError;
|
||||
const std::optional<VoxelForgeCore::WorldAssemblyManifest> Manifest =
|
||||
VoxelForgeCore::WorldAssemblyManifest::FromJson(
|
||||
std::string_view(Utf8Json.Get(), static_cast<std::size_t>(Utf8Json.Length())), CoreError);
|
||||
if (Manifest)
|
||||
{
|
||||
TSharedPtr<FCoreSurfaceSnapshot, ESPMode::ThreadSafe> Snapshot =
|
||||
MakeShared<FCoreSurfaceSnapshot, ESPMode::ThreadSafe>();
|
||||
Snapshot->Recipe = Manifest->recipe;
|
||||
for (const auto& Plan : Manifest->plan.strates)
|
||||
{
|
||||
Snapshot->Plans.Add(Plan.strateIndex, Plan);
|
||||
}
|
||||
NativeSurfaceSnapshot = Snapshot;
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("[VoxelContentManager] native surface snapshot rejected: %s"),
|
||||
UTF8_TO_TCHAR(CoreError.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PlaneMesh)
|
||||
{
|
||||
@@ -71,6 +211,39 @@ void UVoxelContentManager::Initialize(AActor* InOwner, UVoxelStrateManager* InSt
|
||||
}
|
||||
}
|
||||
|
||||
bool UVoxelContentManager::QueryNativeSurfaceAnchors(
|
||||
int32 StrateIndex, const FVector2D& LocalXYCm, float& OutGroundLocalZCm,
|
||||
bool& bOutHasWater, float& OutWaterLocalZCm) const
|
||||
{
|
||||
OutGroundLocalZCm = 0.0f;
|
||||
bOutHasWater = false;
|
||||
OutWaterLocalZCm = -FLT_MAX;
|
||||
const AVoxelWorld* VoxelWorld = Cast<AVoxelWorld>(Owner.Get());
|
||||
if (VoxelWorld == nullptr || !VoxelWorld->bUseVoxelForgeCore
|
||||
|| !NativeSurfaceSnapshot.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float GroundLegacyVoxels = 0.0f;
|
||||
float CapLegacyVoxels = 0.0f;
|
||||
if (!NativeSurfaceSnapshot->GetHeights(StrateIndex,
|
||||
LocalXYCm.X / VOXEL_SIZE, LocalXYCm.Y / VOXEL_SIZE,
|
||||
GroundLegacyVoxels, CapLegacyVoxels))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
OutGroundLocalZCm = GroundLegacyVoxels * VOXEL_SIZE;
|
||||
bool bHasPlan = false;
|
||||
if (!NativeSurfaceSnapshot->GetWater(StrateIndex, bHasPlan,
|
||||
bOutHasWater, OutWaterLocalZCm)
|
||||
|| !bHasPlan)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void UVoxelContentManager::BeginDestroy()
|
||||
{
|
||||
// Backstop: stop any worker from touching us. EndPlay → NotifyShutdown should have already drained.
|
||||
@@ -185,11 +358,14 @@ void UVoxelContentManager::UpdateDecorations(const FVector& PlayerWorldPos)
|
||||
// All strate/water/density queries are in actor-LOCAL space (StrateManager assumes actor origin;
|
||||
// the mesher builds geometry from voxel*VOXEL_SIZE in local space). Bring the player local.
|
||||
const FVector LocalPlayer = OwnerXf.InverseTransformPosition(PlayerWorldPos);
|
||||
const AVoxelWorld* VoxelWorld = Cast<AVoxelWorld>(OwnerActor);
|
||||
const float ManagerLocalZ = VoxelWorld
|
||||
? VoxelWorld->ToStrateManagerLocalZ(LocalPlayer.Z) : LocalPlayer.Z;
|
||||
|
||||
// Player's strate band (actor-local cm). No strate (inter-strate gap / outside world) ⇒ no decos.
|
||||
float TopZ, BotZ;
|
||||
const bool bInStrate = StrateManager->GetStrateUnrealZRange(LocalPlayer.Z, TopZ, BotZ);
|
||||
const int32 StrateIndex = bInStrate ? StrateManager->GetStrateIndex(LocalPlayer.Z) : INT32_MIN;
|
||||
const bool bInStrate = StrateManager->GetStrateUnrealZRange(ManagerLocalZ, TopZ, BotZ);
|
||||
const int32 StrateIndex = bInStrate ? StrateManager->GetStrateIndex(ManagerLocalZ) : INT32_MIN;
|
||||
|
||||
if (!bInStrate)
|
||||
{
|
||||
@@ -206,17 +382,39 @@ void UVoxelContentManager::UpdateDecorations(const FVector& PlayerWorldPos)
|
||||
|
||||
// Shared per-update strate context (a strate is a horizontal slab → same for every cell).
|
||||
CurrentCtx = FDecoContext();
|
||||
CurrentCtx.TopVoxelZ = TopZ / VOXEL_SIZE;
|
||||
CurrentCtx.BottomVoxelZ = BotZ / VOXEL_SIZE;
|
||||
CurrentCtx.StrateIndex = StrateIndex;
|
||||
CurrentCtx.TopVoxelZ = (VoxelWorld ? VoxelWorld->FromStrateManagerLocalZ(TopZ) : TopZ) / VOXEL_SIZE;
|
||||
CurrentCtx.BottomVoxelZ = (VoxelWorld ? VoxelWorld->FromStrateManagerLocalZ(BotZ) : BotZ) / VOXEL_SIZE;
|
||||
CurrentCtx.RepChunkZ = FMath::FloorToInt(((TopZ + BotZ) * 0.5f / VOXEL_SIZE) / (float)CHUNK_SIZE);
|
||||
const FIntVector RepChunk(PlayerCell.X, PlayerCell.Y, CurrentCtx.RepChunkZ);
|
||||
CurrentCtx.Def = StrateManager->GetStrateForChunk(RepChunk);
|
||||
CurrentCtx.bSurfaceWorld =
|
||||
(StrateManager->GetGeneratorTypeForChunk(RepChunk) == ECaveGeneratorType::SurfaceWorld);
|
||||
CurrentCtx.NativeSurface = NativeSurfaceSnapshot;
|
||||
CurrentCtx.bNativeSurface = VoxelWorld != nullptr && VoxelWorld->bUseVoxelForgeCore
|
||||
&& CurrentCtx.bSurfaceWorld
|
||||
&& CurrentCtx.NativeSurface.IsValid()
|
||||
&& CurrentCtx.NativeSurface->FindPlan(StrateIndex) != nullptr;
|
||||
{
|
||||
const float Wv = StrateManager->GetWaterLevelWorldZForChunk(RepChunk);
|
||||
CurrentCtx.bHasWater = (Wv != -FLT_MAX);
|
||||
CurrentCtx.WaterLocalZ = CurrentCtx.bHasWater ? Wv * VOXEL_SIZE : -FLT_MAX;
|
||||
bool bHasNativePlan = false;
|
||||
bool bNativeWaterEnabled = false;
|
||||
float NativeWaterLocalZ = -FLT_MAX;
|
||||
if (CurrentCtx.bNativeSurface
|
||||
&& CurrentCtx.NativeSurface->GetWater(StrateIndex, bHasNativePlan,
|
||||
bNativeWaterEnabled, NativeWaterLocalZ)
|
||||
&& bHasNativePlan)
|
||||
{
|
||||
CurrentCtx.bHasWater = bNativeWaterEnabled;
|
||||
CurrentCtx.WaterLocalZ = bNativeWaterEnabled ? NativeWaterLocalZ : -FLT_MAX;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float Wv = StrateManager->GetWaterLevelWorldZForChunk(RepChunk);
|
||||
CurrentCtx.bHasWater = (Wv != -FLT_MAX);
|
||||
CurrentCtx.WaterLocalZ = CurrentCtx.bHasWater
|
||||
? (VoxelWorld ? VoxelWorld->FromStrateManagerLocalZ(Wv * VOXEL_SIZE) : Wv * VOXEL_SIZE)
|
||||
: -FLT_MAX;
|
||||
}
|
||||
}
|
||||
// Strate biome field (XY-global → resolved once; the worker picks the dominant biome per COLUMN).
|
||||
CurrentCtx.BiomeCtx = StrateManager->GetBiomeContextForChunk(RepChunk);
|
||||
@@ -513,6 +711,15 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
|
||||
const bool bHasBiomes = Ctx.BiomeCtx.IsValid();
|
||||
|
||||
auto D = [&](float VX, float VY, float VZ) { return Gen->GetDensityAt(VX, VY, VZ); };
|
||||
auto GetSurface = [&](float VX, float VY, float& OutGround, float& OutCap)
|
||||
{
|
||||
if (Ctx.bNativeSurface && Ctx.NativeSurface.IsValid())
|
||||
{
|
||||
return Ctx.NativeSurface->GetHeights(
|
||||
Ctx.StrateIndex, VX, VY, OutGround, OutCap);
|
||||
}
|
||||
return Gen->GetSurfaceHeightAt(VX, VY, Ctx.RepChunkZ, OutGround, OutCap);
|
||||
};
|
||||
|
||||
// Build a companion satellite's transform from its profile + per-instance hash at a snapped surface point
|
||||
// (voxel XY/Z + outward normal). Shared by level-1 and level-2 companions.
|
||||
@@ -756,14 +963,15 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
|
||||
// columns carved away by passages / the (0,0) spine / player diffs (the oracle is the raw
|
||||
// heightfield and doesn't know about carving).
|
||||
float hC, cC, hXp, cXp, hXm, cXm, hYp, cYp, hYm, cYm;
|
||||
if (!Gen->GetSurfaceHeightAt(VX, VY, Ctx.RepChunkZ, hC, cC)) continue;
|
||||
Gen->GetSurfaceHeightAt(VX + 1.0f, VY, Ctx.RepChunkZ, hXp, cXp);
|
||||
Gen->GetSurfaceHeightAt(VX - 1.0f, VY, Ctx.RepChunkZ, hXm, cXm);
|
||||
Gen->GetSurfaceHeightAt(VX, VY + 1.0f, Ctx.RepChunkZ, hYp, cYp);
|
||||
Gen->GetSurfaceHeightAt(VX, VY - 1.0f, Ctx.RepChunkZ, hYm, cYm);
|
||||
if (!GetSurface(VX, VY, hC, cC)) continue;
|
||||
GetSurface(VX + 1.0f, VY, hXp, cXp);
|
||||
GetSurface(VX - 1.0f, VY, hXm, cXm);
|
||||
GetSurface(VX, VY + 1.0f, hYp, cYp);
|
||||
GetSurface(VX, VY - 1.0f, hYm, cYm);
|
||||
|
||||
// Floor (terrain top): outward normal = (-dH/dx, -dH/dy, 1).
|
||||
if (hC >= Ctx.BottomVoxelZ && hC <= Ctx.TopVoxelZ && D(VX, VY, hC) <= 0.5f)
|
||||
if (hC >= Ctx.BottomVoxelZ && hC <= Ctx.TopVoxelZ
|
||||
&& (Ctx.bNativeSurface || D(VX, VY, hC) <= 0.5f))
|
||||
{
|
||||
const float dHdx = (hXp - hXm) * 0.5f;
|
||||
const float dHdy = (hYp - hYm) * 0.5f;
|
||||
@@ -773,7 +981,8 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
|
||||
}
|
||||
|
||||
// Sky-cap ceiling underside: outward normal = (dC/dx, dC/dy, -1). Only if open space below.
|
||||
if (cC > hC + 1.0f && cC <= Ctx.TopVoxelZ && D(VX, VY, cC) <= 0.5f)
|
||||
if (cC > hC + 1.0f && cC <= Ctx.TopVoxelZ
|
||||
&& (Ctx.bNativeSurface || D(VX, VY, cC) <= 0.5f))
|
||||
{
|
||||
const float dCdx = (cXp - cXm) * 0.5f;
|
||||
const float dCdy = (cYp - cYm) * 0.5f;
|
||||
@@ -1132,16 +1341,26 @@ bool UVoxelContentManager::FindLandmarkColumn(const UVoxelGenerator* Gen, const
|
||||
if (Ctx.bSurfaceWorld)
|
||||
{
|
||||
float hC, cC;
|
||||
if (!Gen->GetSurfaceHeightAt(VX, VY, Ctx.RepChunkZ, hC, cC)) return false;
|
||||
auto GetSurface = [&](float SampleX, float SampleY, float& OutGround, float& OutCap)
|
||||
{
|
||||
if (Ctx.bNativeSurface && Ctx.NativeSurface.IsValid())
|
||||
{
|
||||
return Ctx.NativeSurface->GetHeights(
|
||||
Ctx.StrateIndex, SampleX, SampleY, OutGround, OutCap);
|
||||
}
|
||||
return Gen->GetSurfaceHeightAt(
|
||||
SampleX, SampleY, Ctx.RepChunkZ, OutGround, OutCap);
|
||||
};
|
||||
if (!GetSurface(VX, VY, hC, cC)) return false;
|
||||
|
||||
if (Surf == ESurfaceType::Ceiling)
|
||||
{
|
||||
if (!(cC > hC + 1.0f && cC <= Ctx.TopVoxelZ)) return false;
|
||||
float d, cXp, cXm, cYp, cYm;
|
||||
Gen->GetSurfaceHeightAt(VX + 1.0f, VY, Ctx.RepChunkZ, d, cXp);
|
||||
Gen->GetSurfaceHeightAt(VX - 1.0f, VY, Ctx.RepChunkZ, d, cXm);
|
||||
Gen->GetSurfaceHeightAt(VX, VY + 1.0f, Ctx.RepChunkZ, d, cYp);
|
||||
Gen->GetSurfaceHeightAt(VX, VY - 1.0f, Ctx.RepChunkZ, d, cYm);
|
||||
GetSurface(VX + 1.0f, VY, d, cXp);
|
||||
GetSurface(VX - 1.0f, VY, d, cXm);
|
||||
GetSurface(VX, VY + 1.0f, d, cYp);
|
||||
GetSurface(VX, VY - 1.0f, d, cYm);
|
||||
FVector N = OwnerXf.TransformVectorNoScale(
|
||||
FVector((cXp - cXm) * 0.5f, (cYp - cYm) * 0.5f, -1.0f)).GetSafeNormal();
|
||||
if (N.IsNearlyZero()) N = FVector::DownVector;
|
||||
@@ -1149,12 +1368,12 @@ bool UVoxelContentManager::FindLandmarkColumn(const UVoxelGenerator* Gen, const
|
||||
}
|
||||
// Floor / Wall / Any → the terrain top.
|
||||
if (!(hC >= Ctx.BottomVoxelZ && hC <= Ctx.TopVoxelZ)) return false;
|
||||
if (Gen->GetDensityAt(VX, VY, hC) > 0.5f) return false; // carved away (passage/spine/diff)
|
||||
if (!Ctx.bNativeSurface && Gen->GetDensityAt(VX, VY, hC) > 0.5f) return false;
|
||||
float d, hXp, hXm, hYp, hYm;
|
||||
Gen->GetSurfaceHeightAt(VX + 1.0f, VY, Ctx.RepChunkZ, hXp, d);
|
||||
Gen->GetSurfaceHeightAt(VX - 1.0f, VY, Ctx.RepChunkZ, hXm, d);
|
||||
Gen->GetSurfaceHeightAt(VX, VY + 1.0f, Ctx.RepChunkZ, hYp, d);
|
||||
Gen->GetSurfaceHeightAt(VX, VY - 1.0f, Ctx.RepChunkZ, hYm, d);
|
||||
GetSurface(VX + 1.0f, VY, hXp, d);
|
||||
GetSurface(VX - 1.0f, VY, hXm, d);
|
||||
GetSurface(VX, VY + 1.0f, hYp, d);
|
||||
GetSurface(VX, VY - 1.0f, hYm, d);
|
||||
FVector N = OwnerXf.TransformVectorNoScale(
|
||||
FVector(-(hXp - hXm) * 0.5f, -(hYp - hYm) * 0.5f, 1.0f)).GetSafeNormal();
|
||||
if (N.IsNearlyZero()) N = FVector::UpVector;
|
||||
@@ -1382,10 +1601,13 @@ void UVoxelContentManager::UpdateLandmarks(const FVector& PlayerWorldPos)
|
||||
|
||||
const FTransform OwnerXf = OwnerActor->GetActorTransform();
|
||||
const FVector LocalPlayer = OwnerXf.InverseTransformPosition(PlayerWorldPos);
|
||||
const AVoxelWorld* VoxelWorld = Cast<AVoxelWorld>(OwnerActor);
|
||||
const float ManagerLocalZ = VoxelWorld
|
||||
? VoxelWorld->ToStrateManagerLocalZ(LocalPlayer.Z) : LocalPlayer.Z;
|
||||
|
||||
float TopZ, BotZ;
|
||||
const bool bInStrate = StrateManager->GetStrateUnrealZRange(LocalPlayer.Z, TopZ, BotZ);
|
||||
const int32 StrateIndex = bInStrate ? StrateManager->GetStrateIndex(LocalPlayer.Z) : INT32_MIN;
|
||||
const bool bInStrate = StrateManager->GetStrateUnrealZRange(ManagerLocalZ, TopZ, BotZ);
|
||||
const int32 StrateIndex = bInStrate ? StrateManager->GetStrateIndex(ManagerLocalZ) : INT32_MIN;
|
||||
|
||||
if (!bInStrate)
|
||||
{
|
||||
@@ -1403,8 +1625,9 @@ void UVoxelContentManager::UpdateLandmarks(const FVector& PlayerWorldPos)
|
||||
|
||||
// Shared strate context (a strate is a horizontal slab → same everywhere this update).
|
||||
FDecoContext Ctx;
|
||||
Ctx.TopVoxelZ = TopZ / VOXEL_SIZE;
|
||||
Ctx.BottomVoxelZ = BotZ / VOXEL_SIZE;
|
||||
Ctx.StrateIndex = StrateIndex;
|
||||
Ctx.TopVoxelZ = (VoxelWorld ? VoxelWorld->FromStrateManagerLocalZ(TopZ) : TopZ) / VOXEL_SIZE;
|
||||
Ctx.BottomVoxelZ = (VoxelWorld ? VoxelWorld->FromStrateManagerLocalZ(BotZ) : BotZ) / VOXEL_SIZE;
|
||||
Ctx.RepChunkZ = FMath::FloorToInt(((TopZ + BotZ) * 0.5f / VOXEL_SIZE) / (float)CHUNK_SIZE);
|
||||
const FIntVector RepChunk(FMath::FloorToInt(LocalPlayer.X / ChunkWorld),
|
||||
FMath::FloorToInt(LocalPlayer.Y / ChunkWorld), Ctx.RepChunkZ);
|
||||
@@ -1416,10 +1639,30 @@ void UVoxelContentManager::UpdateLandmarks(const FVector& PlayerWorldPos)
|
||||
}
|
||||
Ctx.Def = Def;
|
||||
Ctx.bSurfaceWorld = (StrateManager->GetGeneratorTypeForChunk(RepChunk) == ECaveGeneratorType::SurfaceWorld);
|
||||
Ctx.NativeSurface = NativeSurfaceSnapshot;
|
||||
Ctx.bNativeSurface = VoxelWorld != nullptr && VoxelWorld->bUseVoxelForgeCore
|
||||
&& Ctx.bSurfaceWorld && Ctx.NativeSurface.IsValid()
|
||||
&& Ctx.NativeSurface->FindPlan(StrateIndex) != nullptr;
|
||||
{
|
||||
const float Wv = StrateManager->GetWaterLevelWorldZForChunk(RepChunk);
|
||||
Ctx.bHasWater = (Wv != -FLT_MAX);
|
||||
Ctx.WaterLocalZ = Ctx.bHasWater ? Wv * VOXEL_SIZE : -FLT_MAX;
|
||||
bool bHasNativePlan = false;
|
||||
bool bNativeWaterEnabled = false;
|
||||
float NativeWaterLocalZ = -FLT_MAX;
|
||||
if (Ctx.bNativeSurface
|
||||
&& Ctx.NativeSurface->GetWater(StrateIndex, bHasNativePlan,
|
||||
bNativeWaterEnabled, NativeWaterLocalZ)
|
||||
&& bHasNativePlan)
|
||||
{
|
||||
Ctx.bHasWater = bNativeWaterEnabled;
|
||||
Ctx.WaterLocalZ = bNativeWaterEnabled ? NativeWaterLocalZ : -FLT_MAX;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float Wv = StrateManager->GetWaterLevelWorldZForChunk(RepChunk);
|
||||
Ctx.bHasWater = (Wv != -FLT_MAX);
|
||||
Ctx.WaterLocalZ = Ctx.bHasWater
|
||||
? (VoxelWorld ? VoxelWorld->FromStrateManagerLocalZ(Wv * VOXEL_SIZE) : Wv * VOXEL_SIZE)
|
||||
: -FLT_MAX;
|
||||
}
|
||||
}
|
||||
// Landmarks use the same per-column biome gates as decorations. Keep the complete POD biome
|
||||
// context in the worker/game-thread placement context; a null/default context would silently
|
||||
@@ -1585,16 +1828,51 @@ void UVoxelContentManager::UpdateWater(const FVector& PlayerWorldPos)
|
||||
|
||||
const FTransform Xf = OwnerActor->GetActorTransform();
|
||||
const FVector LocalPos = Xf.InverseTransformPosition(PlayerWorldPos);
|
||||
const AVoxelWorld* VoxelWorld = Cast<AVoxelWorld>(OwnerActor);
|
||||
const float LocalToManagerScale = VoxelWorld
|
||||
? VOXEL_SIZE / VoxelWorld->GetActiveVoxelSizeCm() : 1.0f;
|
||||
FVector ManagerLocalPos = LocalPos * LocalToManagerScale;
|
||||
if (VoxelWorld)
|
||||
{
|
||||
ManagerLocalPos.Z = VoxelWorld->ToStrateManagerLocalZ(LocalPos.Z);
|
||||
}
|
||||
|
||||
// Player chunk (voxel→chunk) for the strate / water-level lookup.
|
||||
const float ChunkWorld = (float)CHUNK_SIZE * VOXEL_SIZE; // 1 chunk footprint in cm
|
||||
const FIntVector PlayerChunk(
|
||||
FMath::FloorToInt(LocalPos.X / ChunkWorld),
|
||||
FMath::FloorToInt(LocalPos.Y / ChunkWorld),
|
||||
FMath::FloorToInt(LocalPos.Z / ChunkWorld));
|
||||
FMath::FloorToInt(ManagerLocalPos.X / ChunkWorld),
|
||||
FMath::FloorToInt(ManagerLocalPos.Y / ChunkWorld),
|
||||
FMath::FloorToInt(ManagerLocalPos.Z / ChunkWorld));
|
||||
|
||||
const float WaterVoxelZ = StrateManager->GetWaterLevelWorldZForChunk(PlayerChunk);
|
||||
if (WaterVoxelZ == -FLT_MAX) // current strate has no water → hide the plane
|
||||
float WaterLocalZ = -FLT_MAX;
|
||||
const int32 StrateIndex = StrateManager->GetStrateIndex(ManagerLocalPos.Z);
|
||||
bool bHasNativePlan = false;
|
||||
bool bNativeWaterEnabled = false;
|
||||
float NativeWaterLocalZ = -FLT_MAX;
|
||||
if (VoxelWorld != nullptr && VoxelWorld->bUseVoxelForgeCore
|
||||
&& NativeSurfaceSnapshot.IsValid()
|
||||
&& NativeSurfaceSnapshot->GetWater(StrateIndex, bHasNativePlan,
|
||||
bNativeWaterEnabled, NativeWaterLocalZ)
|
||||
&& bHasNativePlan)
|
||||
{
|
||||
if (bNativeWaterEnabled)
|
||||
{
|
||||
WaterLocalZ = NativeWaterLocalZ;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const float WaterVoxelZ = StrateManager->GetWaterLevelWorldZForChunk(PlayerChunk);
|
||||
if (WaterVoxelZ != -FLT_MAX)
|
||||
{
|
||||
const float ManagerWaterLocalZ = WaterVoxelZ * VOXEL_SIZE;
|
||||
WaterLocalZ = VoxelWorld
|
||||
? VoxelWorld->FromStrateManagerLocalZ(ManagerWaterLocalZ) : ManagerWaterLocalZ;
|
||||
}
|
||||
}
|
||||
|
||||
// No water in this strate means the plane stays hidden. / Sans eau dans cette strate, le plan reste masque.
|
||||
if (WaterLocalZ == -FLT_MAX)
|
||||
{
|
||||
if (WaterPlane) { WaterPlane->SetVisibility(false); }
|
||||
LastWaterZ = -FLT_MAX;
|
||||
@@ -1609,7 +1887,7 @@ void UVoxelContentManager::UpdateWater(const FVector& PlayerWorldPos)
|
||||
FMath::FloorToInt(LocalPos.X / ChunkWorld),
|
||||
FMath::FloorToInt(LocalPos.Y / ChunkWorld));
|
||||
|
||||
if (WaterPlane && WaterPlane->IsVisible() && WaterVoxelZ == LastWaterZ && Cell == LastWaterCell)
|
||||
if (WaterPlane && WaterPlane->IsVisible() && WaterLocalZ == LastWaterZ && Cell == LastWaterCell)
|
||||
return; // nothing changed
|
||||
|
||||
if (!WaterPlane)
|
||||
@@ -1632,7 +1910,7 @@ void UVoxelContentManager::UpdateWater(const FVector& PlayerWorldPos)
|
||||
|
||||
const FVector LocalCenter(((float)Cell.X + 0.5f) * ChunkWorld,
|
||||
((float)Cell.Y + 0.5f) * ChunkWorld,
|
||||
WaterVoxelZ * VOXEL_SIZE);
|
||||
WaterLocalZ);
|
||||
WaterPlane->SetWorldLocation(Xf.TransformPosition(LocalCenter));
|
||||
WaterPlane->SetVisibility(true);
|
||||
|
||||
@@ -1641,7 +1919,7 @@ void UVoxelContentManager::UpdateWater(const FVector& PlayerWorldPos)
|
||||
if (Def->WaterMaterial) { WaterPlane->SetMaterial(0, Def->WaterMaterial); }
|
||||
}
|
||||
|
||||
LastWaterZ = WaterVoxelZ;
|
||||
LastWaterZ = WaterLocalZ;
|
||||
LastWaterCell = Cell;
|
||||
}
|
||||
|
||||
|
||||
@@ -119,6 +119,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
// Cell size in voxels. No upper clamp: coarse clipmap levels use bigger steps (the EXTENT
|
||||
// grows). Coarse tiles also use FEWER cells (InCellsPerAxis) for cheaper gen.
|
||||
Step = FMath::Max(1, Step);
|
||||
const float GeometryVoxelSize = VoxelMath::IsFinite(GeometryVoxelSizeCm)
|
||||
? FMath::Max(1.0f, GeometryVoxelSizeCm) : VOXEL_SIZE;
|
||||
|
||||
// T2.b — octave bias for THIS tile: drop LODOctaveDrop octaves per Step doubling from
|
||||
// the generator's per-voxel volumetric noise (sub-cell octaves can't shape a coarse
|
||||
@@ -130,7 +132,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
TGuardValue<int32> SampleStepGuard(VoxelGenLOD::SampleStep, Step);
|
||||
|
||||
// World-cm origin of the tile's min corner (positions are built relative to this).
|
||||
const FVector ChunkWorldPos = FVector(OriginVoxels) * VOXEL_SIZE;
|
||||
const FVector ChunkWorldPos = FVector(OriginVoxels) * GeometryVoxelSize;
|
||||
|
||||
//=========================================================================
|
||||
// VERTEX DEDUPLICATION MAP
|
||||
@@ -1120,7 +1122,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
const int32 GY = cy + CornerOffsets[i].Y;
|
||||
const int32 GZ = cz + CornerOffsets[i].Z;
|
||||
Positions[i] = ChunkWorldPos
|
||||
+ FVector(GX * Step, GY * Step, GZ * Step) * VOXEL_SIZE;
|
||||
+ FVector(GX * Step, GY * Step, GZ * Step) * GeometryVoxelSize;
|
||||
Gradients[i] = GradAt(GX, GY, GZ);
|
||||
}
|
||||
if (bSampleCell)
|
||||
@@ -1224,10 +1226,10 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
MeshData.NumSurfaceVertices = MeshData.Vertices.Num();
|
||||
if (bGenerateSkirts && (GroundTris.Num() + CapTris.Num()) > 0)
|
||||
{
|
||||
const float ExtentCm = (float)(CellsPerAxis * Step) * VOXEL_SIZE;
|
||||
const float ExtentCm = (float)(CellsPerAxis * Step) * GeometryVoxelSize;
|
||||
const float MinX = ChunkWorldPos.X, MinY = ChunkWorldPos.Y, MinZ = ChunkWorldPos.Z;
|
||||
const float MaxX = MinX + ExtentCm, MaxY = MinY + ExtentCm, MaxZ = MinZ + ExtentCm;
|
||||
const float SkirtDepth = FMath::Max(1.0f, SkirtCells) * (float)Step * VOXEL_SIZE;
|
||||
const float SkirtDepth = FMath::Max(1.0f, SkirtCells) * (float)Step * GeometryVoxelSize;
|
||||
|
||||
auto OnBoundaryPlane = [&](const FVector& A, const FVector& B) -> bool
|
||||
{
|
||||
|
||||
@@ -2403,6 +2403,7 @@ void AVoxelWorld::BeginPlay()
|
||||
|
||||
Generator->InitializeSettings(Settings);
|
||||
Mesher->SetGenerator(Generator);
|
||||
Mesher->SetGeometryVoxelSizeCm(GetActiveVoxelSizeCm());
|
||||
Mesher->SetShutdownFlag(&bShuttingDown);
|
||||
Mesher->bGenerateSkirts = Settings->bGenerateSkirts;
|
||||
Mesher->SkirtCells = Settings->SkirtCells;
|
||||
@@ -12476,10 +12477,21 @@ int32 AVoxelWorld::GetStrateAtPosition(FVector WorldPosition) const
|
||||
// GetStrateIndex veut des cm ACTEUR-LOCAUX (il convertit cm -> voxels lui-meme).
|
||||
// GetStrateIndex wants actor-LOCAL cm (it does the cm -> voxel conversion itself).
|
||||
const float LocalZCm = WorldToLocalCm(WorldPosition).Z;
|
||||
const float StrateManagerZCm = bUseVoxelForgeCore
|
||||
return StrateManager->GetStrateIndex(ToStrateManagerLocalZ(LocalZCm));
|
||||
}
|
||||
|
||||
float AVoxelWorld::ToStrateManagerLocalZ(float LocalZCm) const
|
||||
{
|
||||
return bUseVoxelForgeCore
|
||||
? (LocalZCm / GetActiveVoxelSizeCm()) * VOXEL_SIZE
|
||||
: LocalZCm;
|
||||
return StrateManager->GetStrateIndex(StrateManagerZCm);
|
||||
}
|
||||
|
||||
float AVoxelWorld::FromStrateManagerLocalZ(float ManagerLocalZCm) const
|
||||
{
|
||||
return bUseVoxelForgeCore
|
||||
? (ManagerLocalZCm / VOXEL_SIZE) * GetActiveVoxelSizeCm()
|
||||
: ManagerLocalZCm;
|
||||
}
|
||||
|
||||
FVoxelBiomeQuery AVoxelWorld::GetBiomeAtWorldLocation(FVector WorldLocation) const
|
||||
@@ -12610,8 +12622,9 @@ void AVoxelWorld::CarveAtPosition(FVector Position, float Radius, float Strength
|
||||
{
|
||||
FVoxelModification Mod;
|
||||
Mod.Center = WorldToLocalVoxel(Position); // world cm → voxel space
|
||||
Mod.Radius = Radius * GetLegacyVoxelToActiveVoxelScale();
|
||||
Mod.Strength = -FMath::Abs(Strength); // force negative for carving
|
||||
const float VoxelScale = GetLegacyVoxelToActiveVoxelScale();
|
||||
Mod.Radius = Radius * VoxelScale;
|
||||
Mod.Strength = -FMath::Abs(Strength) * VoxelScale; // force negative for carving / Force le signe negatif pour creuser.
|
||||
ApplyModification(Mod);
|
||||
}
|
||||
|
||||
@@ -12619,8 +12632,9 @@ void AVoxelWorld::FillAtPosition(FVector Position, float Radius, float Strength)
|
||||
{
|
||||
FVoxelModification Mod;
|
||||
Mod.Center = WorldToLocalVoxel(Position);
|
||||
Mod.Radius = Radius * GetLegacyVoxelToActiveVoxelScale();
|
||||
Mod.Strength = FMath::Abs(Strength); // force positive for filling
|
||||
const float VoxelScale = GetLegacyVoxelToActiveVoxelScale();
|
||||
Mod.Radius = Radius * VoxelScale;
|
||||
Mod.Strength = FMath::Abs(Strength) * VoxelScale; // force positive for filling / Force le signe positif pour remplir.
|
||||
ApplyModification(Mod);
|
||||
}
|
||||
|
||||
@@ -12811,7 +12825,9 @@ void AVoxelWorld::ApplyCoreModification(const FVoxelModification& Modification)
|
||||
Brush.halfExtent = {Modification.BoxExtent.X, Modification.BoxExtent.Y, Modification.BoxExtent.Z};
|
||||
Brush.radius = FMath::Max(0.0f, Modification.Radius);
|
||||
Brush.falloff = FMath::Max(0.0f, Modification.Falloff);
|
||||
Brush.strength = Modification.Strength;
|
||||
// Legacy subtracts the modification offset; Core's densityOffset operation adds it.
|
||||
// Le legacy soustrait le decalage; l'operation densityOffset de Core l'ajoute.
|
||||
Brush.strength = -Modification.Strength;
|
||||
switch (Modification.Shape)
|
||||
{
|
||||
case EVoxelBrushShape::Box:
|
||||
@@ -12971,7 +12987,7 @@ void AVoxelWorld::CarveBox(FVector Position, FVector ExtentVoxels, float Strengt
|
||||
Mod.BoxExtent = ExtentVoxels * VoxelScale;
|
||||
Mod.Falloff *= VoxelScale;
|
||||
Mod.Radius = ExtentVoxels.GetMax() * VoxelScale; // budget proxy
|
||||
Mod.Strength = -FMath::Abs(Strength); // carve
|
||||
Mod.Strength = -FMath::Abs(Strength) * VoxelScale; // carve / creuser
|
||||
ApplyModification(Mod);
|
||||
}
|
||||
|
||||
@@ -12984,7 +13000,7 @@ void AVoxelWorld::FillBox(FVector Position, FVector ExtentVoxels, float Strength
|
||||
Mod.BoxExtent = ExtentVoxels * VoxelScale;
|
||||
Mod.Falloff *= VoxelScale;
|
||||
Mod.Radius = ExtentVoxels.GetMax() * VoxelScale;
|
||||
Mod.Strength = FMath::Abs(Strength); // fill
|
||||
Mod.Strength = FMath::Abs(Strength) * VoxelScale; // fill / remplir
|
||||
ApplyModification(Mod);
|
||||
}
|
||||
|
||||
@@ -12997,7 +13013,7 @@ void AVoxelWorld::CarveCapsule(FVector WorldA, FVector WorldB, float RadiusVoxel
|
||||
const float VoxelScale = GetLegacyVoxelToActiveVoxelScale();
|
||||
Mod.Radius = RadiusVoxels * VoxelScale;
|
||||
Mod.Falloff *= VoxelScale;
|
||||
Mod.Strength = -FMath::Abs(Strength);
|
||||
Mod.Strength = -FMath::Abs(Strength) * VoxelScale;
|
||||
ApplyModification(Mod);
|
||||
}
|
||||
|
||||
@@ -13010,7 +13026,7 @@ void AVoxelWorld::FillCapsule(FVector WorldA, FVector WorldB, float RadiusVoxels
|
||||
const float VoxelScale = GetLegacyVoxelToActiveVoxelScale();
|
||||
Mod.Radius = RadiusVoxels * VoxelScale;
|
||||
Mod.Falloff *= VoxelScale;
|
||||
Mod.Strength = FMath::Abs(Strength);
|
||||
Mod.Strength = FMath::Abs(Strength) * VoxelScale;
|
||||
ApplyModification(Mod);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ class UStaticMesh;
|
||||
class UStaticMeshComponent;
|
||||
class UHierarchicalInstancedStaticMeshComponent;
|
||||
class UMaterialInterface;
|
||||
struct FCoreSurfaceSnapshot;
|
||||
|
||||
// An active mini-sun light orb (a placed FStrateLandmark with bIsLightOrb). The terrain material marches
|
||||
// the density volume toward the nearest of these for raymarched shadows. Plain struct (not reflected);
|
||||
@@ -97,6 +98,11 @@ public:
|
||||
* plane). */
|
||||
void UpdateWater(const FVector& PlayerWorldPos);
|
||||
|
||||
/** Read immutable Core ground/water anchors for placement. / Lit les ancres natives sol/eau pour le placement Core. */
|
||||
bool QueryNativeSurfaceAnchors(int32 StrateIndex, const FVector2D& LocalXYCm,
|
||||
float& OutGroundLocalZCm, bool& bOutHasWater,
|
||||
float& OutWaterLocalZCm) const;
|
||||
|
||||
//--- DECORATIONS (distance-based world grid, async march) ----------------
|
||||
/** Stream decoration cells around the player: recompute the desired set on cell/strate change,
|
||||
* launch async march tasks (capped), and apply finished results budgeted. Call every Tick. */
|
||||
@@ -239,12 +245,15 @@ private:
|
||||
struct FDecoContext
|
||||
{
|
||||
const UVoxelStrateDefinition* Def = nullptr; // game-thread only (biome/decoration resolve)
|
||||
int32 StrateIndex = INDEX_NONE;
|
||||
int32 RepChunkZ = 0; // representative chunk-Z for biome lookups
|
||||
float TopVoxelZ = 0.0f; // strate band, voxel coords (march range)
|
||||
float BottomVoxelZ = 0.0f;
|
||||
float WaterLocalZ = -FLT_MAX; // water surface, actor-local cm (-FLT_MAX = no water)
|
||||
bool bHasWater = false;
|
||||
bool bSurfaceWorld = false; // heightfield archetype → use the GetSurfaceHeightAt oracle
|
||||
bool bNativeSurface = false; // Core manifest is the height oracle and water datum / Le manifeste Core definit le relief et le niveau d'eau.
|
||||
TSharedPtr<const FCoreSurfaceSnapshot, ESPMode::ThreadSafe> NativeSurface;
|
||||
|
||||
// Strate biome field (PODs only → worker-safe). Empty ⇒ biomes disabled for this strate. The
|
||||
// worker resolves the dominant biome PER COLUMN (ResolveBiomeSampleAt) so decoration borders
|
||||
@@ -337,6 +346,10 @@ private:
|
||||
UPROPERTY()
|
||||
UVoxelSettings* Settings = nullptr;
|
||||
|
||||
// Immutable Core manifest snapshot captured by decoration workers; workers never touch UObjects.
|
||||
// Instantane immuable du manifeste Core pour les workers de decoration; aucun worker ne touche aux UObjects.
|
||||
TSharedPtr<const FCoreSurfaceSnapshot, ESPMode::ThreadSafe> NativeSurfaceSnapshot;
|
||||
|
||||
int32 Seed = 0;
|
||||
|
||||
// Engine unit plane (/Engine/BasicShapes/Plane) reused for every water surface.
|
||||
@@ -391,6 +404,6 @@ private:
|
||||
|
||||
// Cached state so UpdateWater is a cheap no-op when neither the water level nor the snapped
|
||||
// player cell changed since last frame.
|
||||
float LastWaterZ = -FLT_MAX; // strate water level (voxel Z)
|
||||
float LastWaterZ = -FLT_MAX; // actor-local water height (cm) / Hauteur d'eau locale a l'acteur (cm).
|
||||
FIntPoint LastWaterCell = FIntPoint(INT32_MIN, INT32_MIN); // snapped XY grid cell
|
||||
};
|
||||
@@ -90,6 +90,10 @@ public:
|
||||
|
||||
void SetGenerator(const UVoxelGenerator* InGenerator) { Generator = InGenerator; }
|
||||
|
||||
// Core tile coordinates use 25 cm voxels; legacy generation keeps 12.5 cm.
|
||||
// Les coordonnees des tuiles Core utilisent des voxels de 25 cm; le legacy reste a 12,5 cm.
|
||||
void SetGeometryVoxelSizeCm(float InVoxelSizeCm) { GeometryVoxelSizeCm = InVoxelSizeCm; }
|
||||
|
||||
// AVoxelWorld installs its bShuttingDown flag here. The commandlet path leaves it null.
|
||||
// This is intentionally read-only: workers may observe shutdown, but never mutate world state.
|
||||
void SetShutdownFlag(const std::atomic<bool>* InShutdownFlag) { ShutdownFlag = InShutdownFlag; }
|
||||
@@ -107,6 +111,7 @@ private:
|
||||
|
||||
const std::atomic<bool>* ShutdownFlag = nullptr;
|
||||
const FVoxelSharedDensityGrid* SharedDensityGrid = nullptr;
|
||||
float GeometryVoxelSizeCm = VOXEL_SIZE;
|
||||
|
||||
public:
|
||||
|
||||
|
||||
@@ -426,7 +426,8 @@ public:
|
||||
* @param Position - World-space center of the carve brush
|
||||
* @param Radius - Brush radius in legacy Runtime voxels (12.5 cm); Core converts it to
|
||||
* 25 cm voxels to preserve the physical brush size.
|
||||
* @param Strength - How aggressively to carve (default 10, higher = deeper)
|
||||
* @param Strength - Legacy Runtime density offset; Core scales it to 25 cm units.
|
||||
* Offset de densite Runtime historique; Core le convertit en unite de 25 cm.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Voxel World|Modification")
|
||||
void CarveAtPosition(FVector Position, float Radius = 6.0f, float Strength = 10.0f);
|
||||
@@ -438,7 +439,8 @@ public:
|
||||
* @param Position - World-space center of the fill brush
|
||||
* @param Radius - Brush radius in legacy Runtime voxels (12.5 cm); Core converts it to
|
||||
* 25 cm voxels to preserve the physical brush size.
|
||||
* @param Strength - How aggressively to fill (default 10, higher = more solid)
|
||||
* @param Strength - Legacy Runtime density offset; Core scales it to 25 cm units.
|
||||
* Offset de densite Runtime historique; Core le convertit en unite de 25 cm.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Voxel World|Modification")
|
||||
void FillAtPosition(FVector Position, float Radius = 6.0f, float Strength = 10.0f);
|
||||
@@ -446,7 +448,8 @@ public:
|
||||
/**
|
||||
* Box brush carve/fill. Position is world-space (Unreal units); ExtentVoxels is the
|
||||
* box half-size in legacy Runtime voxels (12.5 cm). Core converts it to 25 cm voxels.
|
||||
* Strength magnitude controls aggressiveness (sign forced).
|
||||
* Strength is a legacy-voxel density offset; Core scales it to 25 cm units and forces its sign.
|
||||
* La force est un decalage de densite historique; Core la convertit en unite de 25 cm et impose le signe.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Voxel World|Modification")
|
||||
void CarveBox(FVector Position, FVector ExtentVoxels, float Strength = 12.0f);
|
||||
@@ -455,8 +458,9 @@ public:
|
||||
void FillBox(FVector Position, FVector ExtentVoxels, float Strength = 12.0f);
|
||||
|
||||
/**
|
||||
* Capsule brush carve/fill between two world-space points (Unreal units), with a tube radius
|
||||
* in legacy Runtime voxels (12.5 cm). Core converts it to 25 cm voxels.
|
||||
* Capsule brush carve/fill between two world-space points (Unreal units). Tube radius and density offset
|
||||
* use legacy Runtime voxels (12.5 cm); Core converts both to 25 cm units.
|
||||
* Creuse/remplit une capsule entre deux points monde. Rayon et decalage Runtime historique sont convertis en unite Core de 25 cm.
|
||||
* Great for boring tunnels or laying solid pillars/walls.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Voxel World|Modification")
|
||||
@@ -572,6 +576,11 @@ public:
|
||||
UFUNCTION(BlueprintPure, Category = "Voxel World|Strate")
|
||||
int32 GetStrateAtPosition(FVector WorldPosition) const;
|
||||
|
||||
// Convert actor-local physical Z to/from UVoxelStrateManager's legacy 12.5 cm space.
|
||||
// Convertit Z local physique vers/depuis l'espace historique de UVoxelStrateManager.
|
||||
float ToStrateManagerLocalZ(float LocalZCm) const;
|
||||
float FromStrateManagerLocalZ(float ManagerLocalZCm) const;
|
||||
|
||||
/**
|
||||
* Probe the biome field at a world location (e.g. a mouse line-trace hit). Returns the dominant +
|
||||
* neighbour biome, the climate fields, the border blend weight, and the dominant biome's decoration
|
||||
@@ -626,6 +635,8 @@ public:
|
||||
|
||||
private:
|
||||
friend class FScopedGenerationPause;
|
||||
friend class UVoxelContentManager;
|
||||
friend class UVoxelAtmosphereManager;
|
||||
|
||||
#if WITH_EDITOR
|
||||
/** Last editor research-field selection applied to loaded tiles. */
|
||||
|
||||
Reference in new issue
Block a user