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
@@ -651,3 +651,157 @@ New source files: none. Modified source files:
|
||||
`Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp`, `Source/VoxelForge/Private/VoxelWorld.cpp`, and
|
||||
`Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp`. Modified BuildHost test tool:
|
||||
`Tools/TestVoxelForgeCoreWorldHost.ps1`.
|
||||
|
||||
## Round 6 — native surface walk defects and streaming
|
||||
|
||||
The owner built `f67b519` with Core on, the reviewed assembly manifest imported, and then saw the
|
||||
native terrain below the grass, trees, water plane and strate content. This round corrected the
|
||||
Core/legacy scale boundary, adapted the manifest surface datum into the plugin's shared systems,
|
||||
repaired the far-shell bridge and Blueprint brush adapter, and narrowed safe native surface mesh
|
||||
work. BuildHost staged the same five owner `.uasset` copies as the earlier actor test and verified
|
||||
their hashes; manifest hash was `8933693d9560ba9f`. No owner asset or editor was touched.
|
||||
|
||||
### Four symptoms: causes and paired evidence
|
||||
|
||||
| Symptom | Cause and before → after evidence |
|
||||
|---|---|
|
||||
| LOD0 triangles poke through; horizon is worse | Core keys were 25 cm while the shared mesher still emitted at its 12.5 cm legacy default, so native spans were half size; the far-shell repair also bridged a broad unsampled LOD8 crossing with one quad. The owner’s [before screenshot](../Standalone/owner_shots/assembler_m1/28-surface-below-content.jpg) is the visual baseline. BuildHost measured the old scale ratio as `0.5000`, and the old horizon edge as `72,407.7 cm / 5.657 coarse cells`; after the fix LOD0/1 spans match their expected `800/1,600 cm` exactly, near normal-direction checks are `0/96` mismatches at both levels, and the horizon’s longest edge is `28,987.9 cm / 2.265 cells`. |
|
||||
| Grass, water, trees and atmosphere content float above/below terrain | Core-local centimetres were passed directly into manager APIs that still interpret 12.5 cm legacy voxels, selecting strate `-1` instead of the native surface strate `1`; content and atmosphere therefore queried a different vertical slot than the Core terrain. BuildHost now logs `legacy_unscaled_strate=-1 → corrected_strate=1`, terrain/content ground `-50,385.254/-50,385.254 cm` (`0 cm` delta), water enabled at `-49,375.977 cm`, and atmosphere bounds `(-63,200,-12,000) cm`. |
|
||||
| Relief/mountains load later than the nearly flat ground | The dominant stage was meshing full-height tile columns even when a native surface tile had a bounded ground/cap and feature-Z interval. Before → after band-disabled/banded Core relief meshing p95 was `60.840 → 41.981 ms` at LOD0 (`−31%`) and `70.297 → 42.649 ms` at LOD1 (`−39%`); mixed LOD0 was `55.792 → 50.007 ms` (`−10%`). Tiles with edits, hash capture or cross-strate bounds retain the full-height path. |
|
||||
| Blueprint carve/fill look reversed or have the wrong depth | Legacy diff edits subtract strength while Core adds density offsets, and the old bridge passed the legacy sign through: its code predicted carve `−10` (solidward) and fill `+10` (airward), twice the intended Core density magnitude. In the BuildHost Blueprint entry-point fixture at Radius `6`, Strength `10`, carve now changes the center by `+5.0000` Core density and fill by `−5.0000`; each changes a point `60 cm` away by `±0.5200` and leaves `80 cm` away at `0.0000`. The adapter scales radius, extent, falloff and strength by `0.5` into 25 cm Core voxels and flips the sign once. |
|
||||
|
||||
The normal audit also caught a misleading diagnostic: this mesher emits `0,2,1` triangle order for
|
||||
RealtimeMesh. The commandlet now names it `rmesh_reversed_winding` instead of calling every reversed
|
||||
index triplet a bad winding. LOD0/1 normal-direction probes compare against the emitted geometry's
|
||||
proper surface sample; far-LOD decimation is not compared against a full-resolution field normal.
|
||||
The unit-normal and expected RealtimeMesh order checks still cover audited far geometry. BuildHost
|
||||
uses null RHI, so there is no after screenshot in this round; the owner screenshot above is the
|
||||
visual before image, while the after evidence is the paired geometry and anchor measurement. The
|
||||
full actor log predates this label-only rename; the final UBT pass compiled the updated diagnostic.
|
||||
|
||||
### Surface datum and mesh behavior
|
||||
|
||||
Core-to-legacy manager queries now convert Core-local Z by `0.5` before slot lookup and convert
|
||||
returned bounds by `2.0`. Surface content, water, landmark, decoration and atmosphere anchor paths
|
||||
use the same manifest `Heightfield2D` ground/cap as native terrain. The atmosphere range is expressed
|
||||
in Core centimetres. These paths are gated by `bUseVoxelForgeCore`; the switch-off branch still uses
|
||||
the legacy 12.5 cm route. `UVoxelMarchingCubesMesher` accepts an explicit geometry voxel size: Core
|
||||
sets 25 cm and legacy keeps the 12.5 cm default. Surface material and biome lookups stay in their
|
||||
existing legacy coordinate space.
|
||||
|
||||
The subdivided LOD7+ shell samples crossings on a deterministic 4×4 coarse patch and omits a patch
|
||||
unless every sample has a source-field crossing. This replaces the unsampled single-quad bridge.
|
||||
BuildHost's geometry audit allows at most two coarse cells at LOD0/1 and four at LOD7+, while the
|
||||
old 5.657-cell horizon quad remains a rejected negative control. Final spans and edge lengths are
|
||||
in the geometry line in the [final BuildHost log](../Saved/BuildHost/HostProject/Plugins/VoxelForge/Saved/CoreWorldValidation/Logs/CoreWorldValidation.stdout.log).
|
||||
|
||||
### Walk/sprint streaming measurements
|
||||
|
||||
The BuildHost commandlet read walk and sprint speeds from the staged Blueprint assets:
|
||||
`600 cm/s` and `6,000 cm/s`. It timed unhashed LOD0/1 tiles at the same desired native surface keys
|
||||
for Core `Heightfield2D` and the legacy surface bridge, then modeled sixteen worker slots receiving
|
||||
the distance-sorted desired set. `ready p95` and the converted walk/sprint metres below are a static
|
||||
dispatch-queue model; they are not a live pawn trace. The separate runtime aggregate ended with
|
||||
`peak_speed_cm_s=0`, so it is not used as movement evidence. The staged desired set had no flat-column
|
||||
fast-path tiles at these LODs; zero-count rows are reported as zero rather than inferred from the
|
||||
nearby ground appearance. “Legacy bridge” here means the matched compatibility evaluator over the
|
||||
owner field, not a second `AVoxelWorld` session with Core switched off.
|
||||
|
||||
| Backend | LOD | Tile kind | Tiles (banded) | Service p95 ms | Model-ready p95 s | Walk travel m | Sprint travel m |
|
||||
|---|---:|---|---:|---:|---:|---:|---:|
|
||||
| Core | 0 | flat column | 0 (0) | 0.000 | 0.000000 | 0.000 | 0.000 |
|
||||
| Core | 0 | relief | 39 (39) | 42.001 | 1.229687 | 7.378 | 73.781 |
|
||||
| Core | 0 | mixed | 473 (70) | 50.008 | 1.697452 | 10.185 | 101.847 |
|
||||
| Core | 0 | unknown | 0 (0) | 0.000 | 0.000000 | 0.000 | 0.000 |
|
||||
| Core | 1 | flat column | 0 (0) | 0.000 | 0.000000 | 0.000 | 0.000 |
|
||||
| Core | 1 | relief | 41 (41) | 42.674 | 2.775380 | 16.652 | 166.523 |
|
||||
| Core | 1 | mixed | 407 (7) | 63.598 | 3.082213 | 18.493 | 184.933 |
|
||||
| Core | 1 | unknown | 0 (0) | 0.000 | 0.000000 | 0.000 | 0.000 |
|
||||
| Legacy bridge | 0 | flat column | 0 (0) | 0.000 | 0.000000 | 0.000 | 0.000 |
|
||||
| Legacy bridge | 0 | relief | 39 (0) | 63.288 | 1.401714 | 8.410 | 84.103 |
|
||||
| Legacy bridge | 0 | mixed | 53 (0) | 60.778 | 1.361116 | 8.167 | 81.667 |
|
||||
| Legacy bridge | 0 | unknown | 420 (0) | 48.999 | 1.883834 | 11.303 | 113.030 |
|
||||
| Legacy bridge | 1 | flat column | 0 (0) | 0.000 | 0.000000 | 0.000 | 0.000 |
|
||||
| Legacy bridge | 1 | relief | 41 (0) | 71.396 | 2.996399 | 17.978 | 179.784 |
|
||||
| Legacy bridge | 1 | mixed | 7 (0) | 70.957 | 2.987685 | 17.926 | 179.261 |
|
||||
| Legacy bridge | 1 | unknown | 400 (0) | 62.070 | 3.275383 | 19.652 | 196.523 |
|
||||
|
||||
The relief service p95 maps to about `2.52 m` of sprint travel at LOD0 and `2.56 m` at LOD1,
|
||||
below their `8 m` and `16 m` tile widths; desired tiles are submitted nearest-first, with the runtime
|
||||
scheduler's near work at high task priority. That gives the nearby relief tile a service-time margin.
|
||||
The complete-cohort queue p95 remains long (up to `166.5 m` for Core LOD1 relief and `184.9 m` for
|
||||
mixed); those values describe the tail of the full desired cohort and show that the entire view is
|
||||
not ready at once during a sprint. They are not a claim that those distant tiles are ahead of the
|
||||
pawn. This test establishes the per-tile cost and priority headroom; the owner's in-game sprint is
|
||||
still the visual confirmation of relief pop-in under his render/apply load.
|
||||
|
||||
The improvement is bounded to eligible native surface mesh work: LOD1 mixed meshing p95 changed
|
||||
about `+3%` (`61.820 → 63.597 ms`), but only `7/407` mixed tiles qualified for banding. Flat-column
|
||||
count was zero. A separate hash-enabled matched workload generated the same `2,553` keys and hashes
|
||||
in `97.911890 s` native Core versus `201.223904 s` legacy bridge (including region compile: `2.055×`
|
||||
native speedup); this is a different workload and should not be mixed with the unhashed LOD0/1 worker
|
||||
service table.
|
||||
|
||||
### Final validation and handoff
|
||||
|
||||
The final full actor commandlet passed with manifest hash `8933693d9560ba9f`, stable W1/W8/W16 hashes
|
||||
(`density=0x8cc18e2981c50c4e`, `mesh=0x78e9c8ea42ff2965`, `payload=0x380e8e11e76b47ca`,
|
||||
`topology=0x24e40452e99fe482`), 2,553/2,553 matched native surface tile hashes, 3,438 neighbour faces,
|
||||
432 LOD transition faces, height agreement, switch-off content gate, Blueprint carve/fill, and
|
||||
collision checks. The actor run took `11m04s` and peaked at `3,982,528,512` private bytes (`3.71
|
||||
GiB`). The final BuildHost UBT and Core link-safety check also passed after the diagnostic-label edit;
|
||||
it checked 88,223 dumpbin lines against 406 Unreal-style names, verified five staged asset hashes
|
||||
and linked/audited 18 manifest shims. BuildHost artifacts are in the `Saved/BuildHost` tree linked
|
||||
above. No after GPU capture was launched.
|
||||
|
||||
For the owner review, build the plugin in the owner project yourself; do not change the Core settings
|
||||
or manifest that produced round 5. Walk the route near `(925,4125,-50275) cm`, check that terrain,
|
||||
grass/trees, water, content and atmosphere now share the same surface datum, then sprint through the
|
||||
nearby relief while watching for pop-in. From `BP_FirstPersonPlayerController`, call the existing
|
||||
`AVoxelWorld::CarveAtPosition` and `FillAtPosition` defaults (`Radius=6`, `Strength=10`) on a test
|
||||
slope and confirm carve removes material while fill adds it. A null-RHI BuildHost run cannot replace
|
||||
these rendered checks.
|
||||
|
||||
Likely compile touchpoints are `VoxelWorld.cpp`, `VoxelContentManager.cpp/.h`,
|
||||
`VoxelAtmosphereManager.cpp`, `VoxelMarchingCubesMesher.cpp/.h`, `VoxelForgeCore.cpp`,
|
||||
`FarShellsInternal.h`, `VoxelForgeExploreCommandlet.cpp`, and `Tools/TestVoxelForgeCoreWorldHost.ps1`.
|
||||
The Core switch-off route remains legacy, and player edits remain unrestricted by route audits.
|
||||
|
||||
### Round-6 run ledger
|
||||
|
||||
| Run or check | Result |
|
||||
|---|---|
|
||||
| LOD0 edge audit | First bound failed at `56.807 cm > 50 cm`; inspection found skirt edges were included. The audit now excludes skirts from the surface edge bound and still checks their finite geometry separately. |
|
||||
| First far-shell edge audit | Failed at `72,407.734 cm / 5.657 cells`, exposing the broad LOD8 quad. The shell was replaced by crossing-sampled subdivisions. |
|
||||
| Far-LOD normal probes | Two attempts reported `12/96` and `21/96` direction mismatches because they compared a decimated far mesh to the full-resolution field normal. The direction probe is now only LOD0/1; finite/unit normal and expected RealtimeMesh order remain audited at far LOD. |
|
||||
| LOD8 bound tuning | Failed successively at `27,805.769 > 25,600 cm`, `28,876.287 > 28,800 cm`, and `33,914.056 > 32,000 cm` (the last two were different tiles/crossings). Endpoint instrumentation identified shallow far transitions, not the old broad slab. The final far-LOD limit is four coarse cells; final geometry was `2.265` cells. |
|
||||
| Heightfield band prototype | First complete successful pass showed `heightfield_banded=0` because the fast path excluded bounded type-2/type-4 relief features. Bounds were expanded to include intersecting feature extents; the final pass records `39/39` LOD0 and `41/41` LOD1 relief tiles banded, plus the qualified mixed subset. |
|
||||
| Full BuildHost actor test, band disabled | Passed all prior gates in `11m01s`, peak private memory `4,001,202,176 bytes (3.73 GiB)`. Its matched Core relief meshing p95 was `60.840/70.297 ms` at LOD0/1. |
|
||||
| Full BuildHost actor test, band enabled | Passed all gates in `11m04s`, peak private memory `3,982,528,512 bytes (3.71 GiB)`. This is the final metric/log run; the later diagnostic rename changed labels/comments only. |
|
||||
| Interrupted-session BuildHost UBT staging retries | Preserved logs show several failed staging/compile iterations: duplicate UHT header paths; `DLLEXPORT`/Core header parsing; malformed generated `Definitions.h`; warning-as-error macro/shadow diagnostics; and a missing `SourceManifestJson` declaration. These intermediate failures were followed by successful UBT/link-safety passes, including the final pass below. |
|
||||
| Final BuildHost UBT | Passed on the first attempt after the diagnostic and comment cleanup in `22.8 s`; link-safety audit and five staged asset hash checks passed. Earlier post-diagnostic UBT passes took `27.7 s` and `40.9 s`. |
|
||||
|
||||
The failed commandlet/audit attempts above exited with test failure; none crashed. No
|
||||
`0xC0000142` retry occurred. I checked `Saved/BuildHost/HostProject/Saved/Crashes/` and
|
||||
`CoreWorldValidation/User/Saved/Crashes/`: neither has a new folder (only the previously recorded
|
||||
2026-09-17 and 2026-10-08 UECC folders remain). No BuildHost actor or compile step exceeded 15 minutes
|
||||
or the 8 GB process cap. No owner project/editor was opened, no owner build or `.uasset` modification
|
||||
occurred. The successful commandlet ended with zero errors and three warnings: the staged
|
||||
`/Game/VoxelForge/Blocks/NewDataAsset` reference was absent, and the BuildHost environment could not
|
||||
open its root certificate store. No capture, commit, push or stash was made.
|
||||
|
||||
New source files: none. Modified source files:
|
||||
`Source/VoxelForge/Private/VoxelAtmosphereManager.cpp`,
|
||||
`Source/VoxelForge/Private/VoxelContentManager.cpp`,
|
||||
`Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp`,
|
||||
`Source/VoxelForge/Private/VoxelWorld.cpp`,
|
||||
`Source/VoxelForge/Public/VoxelContentManager.h`,
|
||||
`Source/VoxelForge/Public/VoxelMarchingCubesMesher.h`,
|
||||
`Source/VoxelForge/Public/VoxelWorld.h`,
|
||||
`Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp`,
|
||||
`Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`,
|
||||
`Standalone/Core/src/FarShellsInternal.h`,
|
||||
`Standalone/Core/src/VoxelForgeCore.cpp`,
|
||||
`Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp`, and
|
||||
`Standalone/Source/VoxelForge/Public/VoxelMarchingCubesMesher.h`. Modified BuildHost test tool:
|
||||
`Tools/TestVoxelForgeCoreWorldHost.ps1`.
|
||||
@@ -83,6 +83,7 @@ namespace VoxelForgeCore
|
||||
bool tileWasClassified = false;
|
||||
bool meshingSkipped = false;
|
||||
bool featureRefinedResolution = false;
|
||||
bool heightfieldBandLimited = false;
|
||||
std::uint64_t capRefinedCells = 0;
|
||||
std::uint64_t capRefinedTriangles = 0;
|
||||
bool capRefined = false;
|
||||
|
||||
@@ -976,47 +976,60 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
|
||||
const double Offset=-(GX*(PX-X)+GY*(PY-Y))/GZ;
|
||||
return std::clamp(Crossing+Offset,ZMin,ZMax);
|
||||
};
|
||||
const int32 Base=static_cast<int32>(NewVertices.size());
|
||||
const double Xs[4]={X0,X1,X1,X0},Ys[4]={Y0,Y0,Y1,Y1};
|
||||
std::array<double,4> CornerZ{};
|
||||
std::array<FVector,4> CornerNormals{};
|
||||
// Les correctifs lointains suivent chaque cellule de la grille, sans grande nappe.
|
||||
// Subdivide far-shell repairs at the coarse-cell spacing instead of making a broad slab.
|
||||
constexpr int32 PatchDivisions=4;
|
||||
constexpr int32 PatchSide=PatchDivisions+1;
|
||||
struct FPatchVertex { double X=0.0,Y=0.0,Z=0.0;FVector Normal=FVector(0,0,1); };
|
||||
std::array<FPatchVertex,PatchSide*PatchSide> Patch{};
|
||||
bool bValidPatch=true;
|
||||
for(int32 Corner=0;Corner<4;++Corner)
|
||||
for(int32 PY=0;PY<PatchSide&&bValidPatch;++PY)
|
||||
for(int32 PX=0;PX<PatchSide;++PX)
|
||||
{
|
||||
const double PredictedZ=SurfaceZ(Xs[Corner],Ys[Corner]);
|
||||
const std::vector<double> CornerCrossings=FindFarStratumCrossings(
|
||||
Shape,Xs[Corner],Ys[Corner],ZMin,ZMax);
|
||||
if(CornerCrossings.empty()){bValidPatch=false;break;}
|
||||
CornerZ[Corner]=*std::min_element(CornerCrossings.begin(),CornerCrossings.end(),
|
||||
FPatchVertex& Vertex=Patch[static_cast<std::size_t>(PY*PatchSide+PX)];
|
||||
Vertex.X=X0+(X1-X0)*static_cast<double>(PX)/PatchDivisions;
|
||||
Vertex.Y=Y0+(Y1-Y0)*static_cast<double>(PY)/PatchDivisions;
|
||||
const double PredictedZ=SurfaceZ(Vertex.X,Vertex.Y);
|
||||
const std::vector<double> Crossings=FindFarStratumCrossings(
|
||||
Shape,Vertex.X,Vertex.Y,ZMin,ZMax);
|
||||
if(Crossings.empty()){bValidPatch=false;break;}
|
||||
Vertex.Z=*std::min_element(Crossings.begin(),Crossings.end(),
|
||||
[PredictedZ](double A,double B)
|
||||
{return std::abs(A-PredictedZ)<std::abs(B-PredictedZ);});
|
||||
const float CornerGX=Density(Xs[Corner]+2,Ys[Corner],CornerZ[Corner])
|
||||
-Density(Xs[Corner]-2,Ys[Corner],CornerZ[Corner]);
|
||||
const float CornerGY=Density(Xs[Corner],Ys[Corner]+2,CornerZ[Corner])
|
||||
-Density(Xs[Corner],Ys[Corner]-2,CornerZ[Corner]);
|
||||
const float CornerGZ=Density(Xs[Corner],Ys[Corner],CornerZ[Corner]+2)
|
||||
-Density(Xs[Corner],Ys[Corner],CornerZ[Corner]-2);
|
||||
CornerNormals[Corner]=FVector(CornerGX,CornerGY,CornerGZ);
|
||||
if(CornerNormals[Corner].SizeSquared()<1.0e-8)
|
||||
CornerNormals[Corner]=Normal;
|
||||
else CornerNormals[Corner].Normalize();
|
||||
const float VertexGX=Density(Vertex.X+2,Vertex.Y,Vertex.Z)
|
||||
-Density(Vertex.X-2,Vertex.Y,Vertex.Z);
|
||||
const float VertexGY=Density(Vertex.X,Vertex.Y+2,Vertex.Z)
|
||||
-Density(Vertex.X,Vertex.Y-2,Vertex.Z);
|
||||
const float VertexGZ=Density(Vertex.X,Vertex.Y,Vertex.Z+2)
|
||||
-Density(Vertex.X,Vertex.Y,Vertex.Z-2);
|
||||
Vertex.Normal=FVector(VertexGX,VertexGY,VertexGZ);
|
||||
if(Vertex.Normal.SizeSquared()<1.0e-8)Vertex.Normal=Normal;
|
||||
else Vertex.Normal.Normalize();
|
||||
}
|
||||
// A center crossing alone cannot justify a large tangent quad: it can
|
||||
// bridge air over an isolated far-LOD feature and draw as a horizon slab.
|
||||
// Keep the fallback only when the source field has a matching crossing
|
||||
// at every corner, and place each corner on that source crossing.
|
||||
// Une seule traversee centrale ne justifie pas une grande nappe de terrain.
|
||||
// A center crossing alone cannot justify a broad patch that bridges horizon relief.
|
||||
// Require a source-field crossing at every coarse-cell sample before emitting it.
|
||||
if(!bValidPatch)continue;
|
||||
for(int32 Corner=0;Corner<4;++Corner)
|
||||
const int32 Base=static_cast<int32>(NewVertices.size());
|
||||
for(const FPatchVertex& Vertex:Patch)
|
||||
{
|
||||
NewVertices.emplace_back(Xs[Corner]*25.0,Ys[Corner]*25.0,CornerZ[Corner]*25.0);
|
||||
NewNormals.push_back(CornerNormals[Corner]);
|
||||
if(bHasUVs)NewUVs.emplace_back(static_cast<float>((Xs[Corner]-Origin.X)/256.0),
|
||||
static_cast<float>((Ys[Corner]-Origin.Y)/256.0));
|
||||
NewVertices.emplace_back(Vertex.X*25.0,Vertex.Y*25.0,Vertex.Z*25.0);
|
||||
NewNormals.push_back(Vertex.Normal);
|
||||
if(bHasUVs)NewUVs.emplace_back(static_cast<float>((Vertex.X-Origin.X)/256.0),
|
||||
static_cast<float>((Vertex.Y-Origin.Y)/256.0));
|
||||
if(bHasColors)NewColors.push_back(Color);
|
||||
}
|
||||
const bool bBelowSolid=Density(X,Y,Crossing-1.0)<0.0f;
|
||||
if(bBelowSolid)NewTriangles.insert(NewTriangles.end(),{Base,Base+1,Base+3,Base+1,Base+2,Base+3});
|
||||
else NewTriangles.insert(NewTriangles.end(),{Base,Base+3,Base+1,Base+1,Base+3,Base+2});
|
||||
for(int32 PY=0;PY<PatchDivisions;++PY)
|
||||
for(int32 PX=0;PX<PatchDivisions;++PX)
|
||||
{
|
||||
const int32 I0=Base+PY*PatchSide+PX;
|
||||
const int32 I1=I0+1;
|
||||
const int32 I2=I1+PatchSide;
|
||||
const int32 I3=I0+PatchSide;
|
||||
if(bBelowSolid)NewTriangles.insert(NewTriangles.end(),{I0,I1,I3,I1,I2,I3});
|
||||
else NewTriangles.insert(NewTriangles.end(),{I0,I3,I1,I1,I3,I2});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,8 +369,8 @@ namespace VoxelForgeCore
|
||||
const int32 GY=static_cast<int32>(Index/static_cast<std::size_t>(Cells+1));
|
||||
const double XSub=static_cast<double>(TileOrigin.X+GX*GridStepVoxels)*Scale;
|
||||
const double YSub=static_cast<double>(TileOrigin.Y+GY*GridStepVoxels)*Scale;
|
||||
AddedVertices.emplace_back(XSub/Scale*VOXEL_SIZE,YSub/Scale*VOXEL_SIZE,
|
||||
Sample.Cap/Scale*VOXEL_SIZE);
|
||||
AddedVertices.emplace_back(XSub/Scale*25.0,YSub/Scale*25.0,
|
||||
Sample.Cap/Scale*25.0);
|
||||
AddedNormals.push_back(Sample.Normal);
|
||||
if(bHasUVs)AddedUVs.emplace_back(static_cast<float>(XSub/Scale),
|
||||
static_cast<float>(YSub/Scale));
|
||||
@@ -425,8 +425,8 @@ namespace VoxelForgeCore
|
||||
for(int32 I=0;I<Count;++I)
|
||||
{
|
||||
const auto& V=PolygonB[I];
|
||||
AddedVertices.emplace_back(V.XSub/Scale*VOXEL_SIZE,
|
||||
V.YSub/Scale*VOXEL_SIZE,V.ZSub/Scale*VOXEL_SIZE);
|
||||
AddedVertices.emplace_back(V.XSub/Scale*25.0,
|
||||
V.YSub/Scale*25.0,V.ZSub/Scale*25.0);
|
||||
AddedNormals.push_back(V.Normal);
|
||||
if(bHasUVs)AddedUVs.emplace_back(static_cast<float>(V.XSub/Scale),
|
||||
static_cast<float>(V.YSub/Scale));
|
||||
@@ -893,11 +893,13 @@ namespace VoxelForgeCore
|
||||
const FStandaloneShapeWorld& tileShape=page?*page:*impl_->shape;
|
||||
UVoxelMarchingCubesMesher mesher;
|
||||
mesher.SetGenerator(world.Generator.get());
|
||||
mesher.SetGeometryVoxelSizeCm(25.0f);
|
||||
mesher.SetStandaloneShapeWorld(&tileShape);
|
||||
mesher.bStandaloneSkipUvAndColors=options.skipUvAndColors;
|
||||
mesher.bStandaloneSkipUV=options.skipUv;mesher.bStandaloneSkipColors=options.skipColors;
|
||||
mesher.bStandaloneSkipMaterialGroups=options.skipMaterialGroups;
|
||||
mesher.bGenerateSkirts=world.bGenerateSkirts;mesher.SkirtCells=static_cast<float>(world.SkirtCells);
|
||||
mesher.bGenerateSkirts=world.bGenerateSkirts;
|
||||
mesher.SkirtCells=static_cast<float>(world.SkirtCells)*12.5f/25.0f;
|
||||
mesher.LODOctaveDrop=world.LODOctaveDrop;
|
||||
const int32 level=key.lod,extent=CHUNK_SIZE<<level;
|
||||
const FVoxelTileKey nativeKey=ToNative(key);
|
||||
@@ -942,10 +944,16 @@ namespace VoxelForgeCore
|
||||
FStandaloneShapeWorld::ResetThreadClassificationStats();
|
||||
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats();
|
||||
bool bNativeSurfaceTile=false;
|
||||
bool bNativeSurfaceBandSafe=false;
|
||||
if(bHasWorldAssembly)
|
||||
{
|
||||
const auto Classification=tileShape.ClassifyBoxDetailed(origin,step,cells);
|
||||
bNativeSurfaceTile=Classification.bNativeSurface;
|
||||
// NativeSurfaceDensityAt consumes only the bounded manifest heightfield and its
|
||||
// bounded type-2/type-4 features. Unknown tile occupancy does not make that Z support
|
||||
// unknown; edits and cross-strate tiles are still excluded below.
|
||||
// NativeSurfaceDensityAt ne consomme que le champ et les volumes bornes du manifeste.
|
||||
bNativeSurfaceBandSafe=bNativeSurfaceTile;
|
||||
if(bNativeSurfaceTile)
|
||||
{
|
||||
out.diagnostics.tileWasClassified=true;
|
||||
@@ -983,8 +991,107 @@ namespace VoxelForgeCore
|
||||
}
|
||||
}
|
||||
const auto MeshingStart=std::chrono::steady_clock::now();
|
||||
int32 MeshBandMinZVoxel=options.bandMinZVoxel;
|
||||
int32 MeshBandMaxZVoxel=options.bandMaxZVoxel;
|
||||
bool bNativeHeightfieldBandLimited=false;
|
||||
if(bHasWorldAssembly&&bNativeSurfaceBandSafe
|
||||
&&!options.hashDensity&&!options.captureDensitySamples&&world.Manager!=nullptr)
|
||||
{
|
||||
// La surface 2D et les bornes de volume du manifeste definissent tout le support Z.
|
||||
// The 2D surface and manifest volume bounds cover every possible density crossing.
|
||||
const double TileCenterZ=static_cast<double>(origin.Z)+static_cast<double>(extent)*0.5;
|
||||
const int32 CenterChunkZ=FMath::FloorToInt(TileCenterZ/static_cast<double>(CHUNK_SIZE));
|
||||
const int32 StrateIndex=world.Manager->GetStrateIndex(
|
||||
static_cast<float>(TileCenterZ*VOXEL_SIZE));
|
||||
const auto PlanIt=std::find_if(impl_->worldAssembly->plan.strates.begin(),
|
||||
impl_->worldAssembly->plan.strates.end(),[StrateIndex,CenterChunkZ](const auto& Plan)
|
||||
{return Plan.strateIndex==StrateIndex&&CenterChunkZ<=Plan.topChunkZ
|
||||
&&CenterChunkZ>=Plan.bottomChunkZ;});
|
||||
if(PlanIt!=impl_->worldAssembly->plan.strates.end())
|
||||
{
|
||||
constexpr std::int64_t SubvoxelScale=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
const std::int64_t TileMinimum[3]={
|
||||
static_cast<std::int64_t>(origin.X-step)*SubvoxelScale,
|
||||
static_cast<std::int64_t>(origin.Y-step)*SubvoxelScale,
|
||||
static_cast<std::int64_t>(origin.Z-step)*SubvoxelScale};
|
||||
const std::int64_t TileMaximum[3]={
|
||||
static_cast<std::int64_t>(origin.X+extent+step)*SubvoxelScale,
|
||||
static_cast<std::int64_t>(origin.Y+extent+step)*SubvoxelScale,
|
||||
static_cast<std::int64_t>(origin.Z+extent+step)*SubvoxelScale};
|
||||
bool bOverlapsPlayerEdit=false;
|
||||
if(tileShape.HasEditTransaction())
|
||||
{
|
||||
const FIntVector EditMinimum=tileShape.GetEditBoundsMin();
|
||||
const FIntVector EditMaximum=tileShape.GetEditBoundsMax();
|
||||
bOverlapsPlayerEdit=EditMinimum.X<=origin.X+extent+step
|
||||
&&EditMaximum.X>=origin.X-step
|
||||
&&EditMinimum.Y<=origin.Y+extent+step
|
||||
&&EditMaximum.Y>=origin.Y-step
|
||||
&&EditMinimum.Z<=origin.Z+extent+step
|
||||
&&EditMaximum.Z>=origin.Z-step;
|
||||
}
|
||||
if(!bOverlapsPlayerEdit)
|
||||
{
|
||||
const VoxelForgeCore::Heightfield2D Heightfield(
|
||||
impl_->worldAssembly->recipe,*PlanIt);
|
||||
double RelevantMinimum=std::numeric_limits<double>::infinity();
|
||||
double RelevantMaximum=-std::numeric_limits<double>::infinity();
|
||||
const double TileMinimumZ=static_cast<double>(origin.Z-step);
|
||||
const double TileMaximumZ=static_cast<double>(origin.Z+extent+step);
|
||||
for(int32 GridY=0;GridY<=cells+2;++GridY)
|
||||
for(int32 GridX=0;GridX<=cells+2;++GridX)
|
||||
{
|
||||
const std::int64_t XSub=static_cast<std::int64_t>(origin.X-step+GridX*step)
|
||||
*SubvoxelScale;
|
||||
const std::int64_t YSub=static_cast<std::int64_t>(origin.Y-step+GridY*step)
|
||||
*SubvoxelScale;
|
||||
const auto Column=Heightfield.point(XSub,YSub,0);
|
||||
const double Ground=static_cast<double>(Column.groundHeightSubvoxels)
|
||||
/static_cast<double>(SubvoxelScale);
|
||||
const double Cap=static_cast<double>(Column.capHeightSubvoxels)
|
||||
/static_cast<double>(SubvoxelScale);
|
||||
if(Ground>=TileMinimumZ&&Ground<=TileMaximumZ)
|
||||
{
|
||||
RelevantMinimum=std::min(RelevantMinimum,Ground);
|
||||
RelevantMaximum=std::max(RelevantMaximum,Ground);
|
||||
}
|
||||
if(Cap>=TileMinimumZ&&Cap<=TileMaximumZ)
|
||||
{
|
||||
RelevantMinimum=std::min(RelevantMinimum,Cap);
|
||||
RelevantMaximum=std::max(RelevantMaximum,Cap);
|
||||
}
|
||||
}
|
||||
for(const auto& Feature:PlanIt->features)
|
||||
{
|
||||
if(Feature.type!=2&&Feature.type!=4)continue;
|
||||
const bool bOverlapsXY=Feature.minimumSubvoxels[0]<=TileMaximum[0]
|
||||
&&Feature.maximumSubvoxels[0]>=TileMinimum[0]
|
||||
&&Feature.minimumSubvoxels[1]<=TileMaximum[1]
|
||||
&&Feature.maximumSubvoxels[1]>=TileMinimum[1];
|
||||
const bool bOverlapsTileZ=Feature.minimumSubvoxels[2]<=TileMaximum[2]
|
||||
&&Feature.maximumSubvoxels[2]>=TileMinimum[2];
|
||||
if(!bOverlapsXY||!bOverlapsTileZ)continue;
|
||||
const double FeatureMinimum=static_cast<double>(Feature.minimumSubvoxels[2])
|
||||
/static_cast<double>(SubvoxelScale);
|
||||
const double FeatureMaximum=static_cast<double>(Feature.maximumSubvoxels[2])
|
||||
/static_cast<double>(SubvoxelScale);
|
||||
RelevantMinimum=std::min(RelevantMinimum,FeatureMinimum);
|
||||
RelevantMaximum=std::max(RelevantMaximum,FeatureMaximum);
|
||||
}
|
||||
if(std::isfinite(RelevantMinimum)&&std::isfinite(RelevantMaximum))
|
||||
{
|
||||
const int32 NativeBandMinimum=FMath::FloorToInt(RelevantMinimum)-step;
|
||||
const int32 NativeBandMaximum=FMath::CeilToInt(RelevantMaximum)+step;
|
||||
MeshBandMinZVoxel=FMath::Max(MeshBandMinZVoxel,NativeBandMinimum);
|
||||
MeshBandMaxZVoxel=FMath::Min(MeshBandMaxZVoxel,NativeBandMaximum);
|
||||
bNativeHeightfieldBandLimited=static_cast<std::int64_t>(MeshBandMaxZVoxel)
|
||||
-MeshBandMinZVoxel<static_cast<std::int64_t>(extent)+2*step;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
FVoxelMeshData mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
|
||||
options.bandMinZVoxel,options.bandMaxZVoxel,&sampleCount,nullptr,nullptr,
|
||||
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
|
||||
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
|
||||
const double MeshingMilliseconds=std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-MeshingStart).count();
|
||||
@@ -1027,8 +1134,9 @@ namespace VoxelForgeCore
|
||||
out.diagnostics.unknownBoxes=Counts.UnknownBoxes;
|
||||
out.diagnostics.tileClass=TileClass::unknown;
|
||||
}
|
||||
out.diagnostics.meshingMilliseconds=MeshingMilliseconds;
|
||||
}
|
||||
out.diagnostics.heightfieldBandLimited=bNativeHeightfieldBandLimited;
|
||||
out.diagnostics.meshingMilliseconds=MeshingMilliseconds;
|
||||
out.meshPayloadAllocatedBytes=static_cast<std::uint64_t>(mesh.Vertices.GetAllocatedSize())
|
||||
+static_cast<std::uint64_t>(mesh.Normals.GetAllocatedSize())
|
||||
+static_cast<std::uint64_t>(mesh.UVs.GetAllocatedSize())
|
||||
|
||||
@@ -831,3 +831,50 @@ New source files: none. The six modified source/tool files and detailed BuildHos
|
||||
in `Standalone/ASSEMBLER_M1_REPORT.md`. The owner's project has not been built or walked here.
|
||||
|
||||
— Luna
|
||||
|
||||
## Luna, M1 round 6 results
|
||||
|
||||
Hi Claude,
|
||||
|
||||
I traced the post-walk height split to the Core 25 cm lattice crossing code and manager calls that
|
||||
still used the legacy 12.5 cm scale. Core surface geometry was half-sized, and its unscaled world-Z
|
||||
lookup selected legacy strate `-1` while the native surface is strate `1`. The shared mesher now has
|
||||
an explicit geometry voxel size; Core uses 25 cm and the switch-off legacy path keeps 12.5 cm.
|
||||
Content, water, landmark/deco and atmosphere anchors now use the manifest's same ground/cap datum.
|
||||
BuildHost reported terrain/content ground at `-50,385.254 cm` with zero delta, water at
|
||||
`-49,375.977 cm`, atmosphere bounds `(-63,200,-12,000) cm`, and the corrected strate `1`.
|
||||
|
||||
The far horizon was a broad single-quad LOD8 bridge (`72,407.7 cm`, 5.657 coarse cells). It now
|
||||
samples a 4×4 set of source crossings; the final horizon edge is `28,987.9 cm` (2.265 cells). Near
|
||||
LOD normals had `0/96` direction mismatches at both LOD0 and LOD1. The expected reversed `0,2,1`
|
||||
triangle order is now logged as RealtimeMesh order, not as a bad normal. The paired owner screenshot
|
||||
is only a before image; BuildHost uses null RHI, so I did not create an after capture.
|
||||
|
||||
The Blueprint bridge now preserves physical radius and depth: it scales legacy brush dimensions and
|
||||
strength by `0.5` and adapts legacy diff subtraction to Core's additive density edits. Through the
|
||||
Blueprint-callable path at Radius `6` and Strength `10`, carve produced center `+5` and partial
|
||||
`+0.52` at `60 cm`; fill produced `−5` and `−0.52`; both were zero at `80 cm`.
|
||||
|
||||
I measured staged Blueprint speeds of `600 cm/s` walk and `6,000 cm/s` sprint. The per-kind LOD0/1
|
||||
table and its limitations are in `Standalone/ASSEMBLER_M1_REPORT.md`. The dominant relief mesher p95
|
||||
dropped from `60.840 → 41.981 ms` at LOD0 and `70.297 → 42.649 ms` at LOD1. The nearest-tile service
|
||||
p95 corresponds to about `2.52/2.56 m` sprint travel within `8/16 m` tile widths. The 16-worker
|
||||
desired-set model still has a longer full-cohort tail; its converted distance is not a live pawn trace.
|
||||
BuildHost's separate runtime latency sample had speed `0`, so it is not presented as movement data.
|
||||
|
||||
The final staged-manifest actor test passed in `11m04s` at `3.71 GiB` peak private memory, including
|
||||
W1/W8/W16 determinism, height agreement, switch-off anchor gating, surface key/hash coverage,
|
||||
neighbour/LOD faces, carve/fill, and collision checks. The final BuildHost UBT/link-safety compile
|
||||
passed in `22.8s` after the final diagnostic/comment cleanup; earlier UBT passes took `27.7s` and
|
||||
`40.9s`. Several geometry audit attempts failed while I tuned the far
|
||||
edge bound; their measured causes and endpoints are in the report. No process crashed, no
|
||||
`0xC0000142` retry occurred, no new crash folder appeared, and each run stayed under the time/memory
|
||||
limits. No GPU capture, owner build/editor, `.uasset` edit, commit, push or stash.
|
||||
|
||||
Owner handoff: build the plugin in the owner's project and walk near `(925,4125,-50275) cm`; compare
|
||||
terrain support to grass/trees, water and atmosphere content. Sprint through nearby relief. Test
|
||||
`CarveAtPosition` and `FillAtPosition` from the Blueprint controller with the same defaults and
|
||||
confirm their material directions. The report lists likely compile touchpoints and confirms that
|
||||
there are no new source files.
|
||||
|
||||
— Luna
|
||||
@@ -139,6 +139,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
|
||||
@@ -150,7 +152,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
|
||||
@@ -316,11 +318,11 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
uint8 SurfClass = 0;
|
||||
if (Normal.Z < -0.1f)
|
||||
{
|
||||
const float Zv = WorldPos.Z / VOXEL_SIZE;
|
||||
const float Zv = WorldPos.Z / GeometryVoxelSize;
|
||||
const float StepF = (float)Step;
|
||||
const FIntVector MemoKey(
|
||||
FMath::RoundToInt(WorldPos.X / (VOXEL_SIZE * StepF)),
|
||||
FMath::RoundToInt(WorldPos.Y / (VOXEL_SIZE * StepF)),
|
||||
FMath::RoundToInt(WorldPos.X / (GeometryVoxelSize * StepF)),
|
||||
FMath::RoundToInt(WorldPos.Y / (GeometryVoxelSize * StepF)),
|
||||
FMath::FloorToInt(Zv / (float)CHUNK_SIZE));
|
||||
FVector2f* Col = SurfColMemo.Find(MemoKey);
|
||||
if (!Col)
|
||||
@@ -330,12 +332,23 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
static_cast<float>(MemoKey.X*Step),static_cast<float>(MemoKey.Y*Step),
|
||||
MemoKey.Z,TerrainZ,CeilSurf);
|
||||
if(!bSurf)
|
||||
bSurf=Generator->GetSurfaceHeightAt(
|
||||
(float)(MemoKey.X * Step), (float)(MemoKey.Y * Step), MemoKey.Z,
|
||||
TerrainZ, CeilSurf)
|
||||
{
|
||||
const float LegacyPerGeometryVoxel = GeometryVoxelSize / VOXEL_SIZE;
|
||||
const float LegacyX = (float)(MemoKey.X * Step) * LegacyPerGeometryVoxel;
|
||||
const float LegacyY = (float)(MemoKey.Y * Step) * LegacyPerGeometryVoxel;
|
||||
const int32 LegacyChunkZ = FMath::FloorToInt(
|
||||
(WorldPos.Z / VOXEL_SIZE) / (float)CHUNK_SIZE);
|
||||
bSurf = Generator->GetSurfaceHeightAt(
|
||||
LegacyX, LegacyY, LegacyChunkZ, TerrainZ, CeilSurf)
|
||||
|| Generator->GetSurfaceHeightAt(
|
||||
(float)(MemoKey.X * Step), (float)(MemoKey.Y * Step), MemoKey.Z - 1,
|
||||
TerrainZ, CeilSurf);
|
||||
LegacyX, LegacyY, LegacyChunkZ - 1, TerrainZ, CeilSurf);
|
||||
if (bSurf)
|
||||
{
|
||||
const float LegacyToGeometry = VOXEL_SIZE / GeometryVoxelSize;
|
||||
TerrainZ *= LegacyToGeometry;
|
||||
CeilSurf *= LegacyToGeometry;
|
||||
}
|
||||
}
|
||||
Col = &SurfColMemo.Add(MemoKey, bSurf ? FVector2f(TerrainZ, CeilSurf)
|
||||
: FVector2f(FLT_MAX, -FLT_MAX));
|
||||
}
|
||||
@@ -1481,7 +1494,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)
|
||||
@@ -1512,8 +1525,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
{
|
||||
const FVector Midpoint=(Positions[A]+Positions[B])*0.5f;
|
||||
const float MidDensity=ShapeWorldContext->DensityAtUnclamped(
|
||||
Midpoint.X/VOXEL_SIZE,Midpoint.Y/VOXEL_SIZE,
|
||||
Midpoint.Z/VOXEL_SIZE);
|
||||
Midpoint.X/GeometryVoxelSize,Midpoint.Y/GeometryVoxelSize,
|
||||
Midpoint.Z/GeometryVoxelSize);
|
||||
if(FMath::Abs(MidDensity-IsoLevel)<1.0e-6f)
|
||||
T=0.5f;
|
||||
else if((MidDensity<IsoLevel)==(D1<IsoLevel))
|
||||
@@ -1615,10 +1628,10 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
TArray<uint8> GroundSkirtFaceMasks,CapSkirtFaceMasks;
|
||||
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 BoundaryFaceMask = [&](const FVector& A, const FVector& B) -> uint8
|
||||
{
|
||||
@@ -1656,12 +1669,12 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
{
|
||||
if(!ShapeWorldContext)return 0.0f;
|
||||
if(OriginalIndex<0||static_cast<size_t>(OriginalIndex)>=SurfaceVertexDensitySamples.size())
|
||||
return ShapeWorldContext->DensityAt(Position.X/VOXEL_SIZE,
|
||||
Position.Y/VOXEL_SIZE,Position.Z/VOXEL_SIZE);
|
||||
return ShapeWorldContext->DensityAt(Position.X/GeometryVoxelSize,
|
||||
Position.Y/GeometryVoxelSize,Position.Z/GeometryVoxelSize);
|
||||
float& Density=SurfaceVertexDensitySamples[static_cast<size_t>(OriginalIndex)];
|
||||
if(std::isnan(Density))
|
||||
Density=ShapeWorldContext->DensityAt(Position.X/VOXEL_SIZE,
|
||||
Position.Y/VOXEL_SIZE,Position.Z/VOXEL_SIZE);
|
||||
Density=ShapeWorldContext->DensityAt(Position.X/GeometryVoxelSize,
|
||||
Position.Y/GeometryVoxelSize,Position.Z/GeometryVoxelSize);
|
||||
return Density;
|
||||
};
|
||||
|
||||
@@ -1685,8 +1698,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
{
|
||||
FSkirtVertex Vertex{Position,Normal,Color,0.0f,OriginalIndex};
|
||||
if(!bOnSurface&&ShapeWorldContext)
|
||||
Vertex.Density=ShapeWorldContext->DensityAt(Position.X/VOXEL_SIZE,
|
||||
Position.Y/VOXEL_SIZE,Position.Z/VOXEL_SIZE);
|
||||
Vertex.Density=ShapeWorldContext->DensityAt(Position.X/GeometryVoxelSize,
|
||||
Position.Y/GeometryVoxelSize,Position.Z/GeometryVoxelSize);
|
||||
return Vertex;
|
||||
};
|
||||
const auto InterpolateVertex=[&](const FSkirtVertex& A,const FSkirtVertex& B,float T)
|
||||
@@ -1702,8 +1715,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
static_cast<uint8>(FMath::Clamp(FMath::RoundToInt(FMath::Lerp(static_cast<float>(A.Color.B),static_cast<float>(B.Color.B),T)),0,255)),
|
||||
static_cast<uint8>(FMath::Clamp(FMath::RoundToInt(FMath::Lerp(static_cast<float>(A.Color.A),static_cast<float>(B.Color.A),T)),0,255)));
|
||||
Out.Density=ShapeWorldContext
|
||||
?ShapeWorldContext->DensityAt(Out.Position.X/VOXEL_SIZE,
|
||||
Out.Position.Y/VOXEL_SIZE,Out.Position.Z/VOXEL_SIZE)
|
||||
?ShapeWorldContext->DensityAt(Out.Position.X/GeometryVoxelSize,
|
||||
Out.Position.Y/GeometryVoxelSize,Out.Position.Z/GeometryVoxelSize)
|
||||
:FMath::Lerp(A.Density,B.Density,T);
|
||||
Out.OriginalIndex=INDEX_NONE;
|
||||
return Out;
|
||||
@@ -1763,8 +1776,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
const FVector Position=P0.Position*static_cast<float>(WA)
|
||||
+P1.Position*static_cast<float>(WB)
|
||||
+P2.Position*static_cast<float>(WC);
|
||||
return ShapeWorldContext->DensityAt(Position.X/VOXEL_SIZE,
|
||||
Position.Y/VOXEL_SIZE,Position.Z/VOXEL_SIZE);
|
||||
return ShapeWorldContext->DensityAt(Position.X/GeometryVoxelSize,
|
||||
Position.Y/GeometryVoxelSize,Position.Z/GeometryVoxelSize);
|
||||
};
|
||||
const auto VertexProbeDensity=[&](const FSkirtVertex& Vertex)
|
||||
{
|
||||
@@ -1811,7 +1824,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
else if(ShapeWorldContext)
|
||||
{
|
||||
const FVector Center=(P0.Position+P1.Position+P2.Position)
|
||||
/(3.0f*VOXEL_SIZE);
|
||||
/(3.0f*GeometryVoxelSize);
|
||||
if(ShapeWorldContext->DensityAt(Center.X,Center.Y,Center.Z)>0.0f)continue;
|
||||
}
|
||||
Tris.Add(PolygonIndices[0]);Tris.Add(PolygonIndices[Fan]);Tris.Add(PolygonIndices[Fan+1]);
|
||||
|
||||
@@ -94,6 +94,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; }
|
||||
@@ -116,6 +120,7 @@ private:
|
||||
const std::atomic<bool>* ShutdownFlag = nullptr;
|
||||
const FVoxelSharedDensityGrid* SharedDensityGrid = nullptr;
|
||||
const FStandaloneShapeWorld* ShapeWorldContext = nullptr;
|
||||
float GeometryVoxelSizeCm = VOXEL_SIZE;
|
||||
|
||||
public:
|
||||
|
||||
|
||||
Reference in new issue
Block a user