Files
VoxelForge/Saved/Research/intervals/interval_spike.cpp
T
Fr0zkaandClaude Opus 5.5 a443eb8546 research(intervals): blends, dual contouring, surface relief -- look better, not there; new stages single-threaded
Bounded smooth blends at junctions (3.1% of cells), QEF dual contouring on the exact crossings and
analytic normals, band-limited surface relief (bound 0.60 m, observed 0.40-0.44 m, R toggles it),
sparser strata. Exact signs vs the blended reference at every node, thin features kept, same hash
across worker counts. Mesh has 0 boundary edges but 3-7 overused edge groups: not collision-ready.
Near CPU total, 6 workers: 241 -> 836 ms. The new stages all run on ONE thread: blend index 186 ms,
blend evaluation 201 ms, dual contouring 322 ms, relief 61 ms.
Look: slab stack gone, joins rounder; chambers read as broad crumpled facets, junction seams still
flaky. Viewer built as interval_viewer2.exe (the owner had the first one open).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-10-02 04:23:38 +02:00

2457 lines
134 KiB
C++

#include <algorithm>
#include <array>
#include <atomic>
#include <bit>
#include <cassert>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <limits>
#include <numeric>
#include <span>
#include <string>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#include <psapi.h>
#pragma comment(lib, "Psapi.lib")
namespace {
using Clock = std::chrono::steady_clock;
constexpr int kCoreCells = 256;
constexpr int kHalo = 1;
constexpr int kNodes = kCoreCells + 1 + 2 * kHalo;
constexpr int kWordsPerLine = (kNodes + 63) / 64;
constexpr int kPageCells = 64;
constexpr int kPagesPerAxis = kCoreCells / kPageCells;
constexpr int kBucketCm = 400;
constexpr int kBucketCount = 17;
constexpr int kShapeLimitPerLine = 64;
constexpr int64_t kQScale = int64_t{1} << 28;
constexpr int64_t kSubPerCm = 16; // canonical interval/sign lattice: 0.625 mm
constexpr int64_t kSurfaceSubPerCm = 32; // crossing positions: 0.3125 mm
constexpr int kCellStepCm = 25;
constexpr int kBlendWidthCm = 70;
constexpr float kReliefBoundMeters = 0.60f;
constexpr int kGridMinCm = -3225;
constexpr int kGridMaxCm = kGridMinCm + (kNodes - 1) * kCellStepCm;
constexpr int kWorldOriginCm[3] = {-12000, 6000, -4500};
constexpr uint64_t kMask64 = ~uint64_t{0};
enum class Operation : uint8_t { CarveAir, AddRock };
enum class FeatureKind : uint8_t {
Room, Tunnel, Pillar, Stalactite, Stalagmite, Shelf, FloatingMass,
Stratum, Crater, CrowdedJunction
};
struct Mat3Q15 { int32_t v[3][3]; };
// Quantized rotations are fixed data, so corpus construction does not depend on libm.
constexpr std::array<Mat3Q15, 8> kRotations = {{
{{{32768,0,0},{0,32768,0},{0,0,32768}}},
{{{23170,-23170,0},{23170,23170,0},{0,0,32768}}},
{{{23170,0,23170},{0,32768,0},{-23170,0,23170}}},
{{{32768,0,0},{0,23170,-23170},{0,23170,23170}}},
{{{0,-32768,0},{32768,0,0},{0,0,32768}}},
{{{0,0,32768},{0,32768,0},{-32768,0,0}}},
{{{32768,0,0},{0,0,-32768},{0,32768,0}}},
{{{16384,-23170,16384},{16384,23170,16384},{-23170,0,23170}}}
}};
struct Shape {
int32_t c[3]{}; // local centimetres
int32_t radius[3]{}; // local centimetres
int32_t rot[3][3]{}; // Q15 rotation, columns are local axes
int64_t q[3][3]{}; // Q28 / cm^2, exact predicate coefficients
int32_t lo[3]{}, hi[3]{}; // conservative local-centimetre AABB, inclusive
Operation op = Operation::CarveAir;
FeatureKind kind = FeatureKind::Room;
uint32_t id = 0;
bool auditThin = false;
};
struct Crossing {
int32_t positionSub32 = 0; // local cm * 32, half of the canonical 1/16 cm grid
uint32_t shapeId = 0;
uint8_t beforeRock = 0;
uint8_t afterRock = 0;
};
constexpr uint32_t kBlendCrossingShape = std::numeric_limits<uint32_t>::max();
struct LineRange {
uint32_t offset = 0;
uint16_t count = 0;
uint16_t candidates = 0;
uint16_t primitiveIntervals = 0;
uint16_t occupancyRuns = 0;
};
struct AxisData {
std::vector<uint64_t> signs;
std::vector<LineRange> lines;
std::vector<Crossing> crossings;
};
struct AxisStats {
double candidateP50 = 0, candidateP95 = 0;
double intervalP50 = 0, intervalP95 = 0;
double crossingP50 = 0, crossingP95 = 0;
double runsP50 = 0, runsP95 = 0;
uint32_t candidateMax = 0, intervalMax = 0, crossingMax = 0, runsMax = 0;
uint64_t candidateTotal = 0, intervalTotal = 0, crossingTotal = 0, runsTotal = 0;
};
struct Vertex {
float x, y, z;
float nx, ny, nz;
};
struct TempVertex {
uint64_t key = 0;
uint32_t ownerPage = 0;
Vertex v{};
};
struct PageRange { uint32_t page, indexStart, indexCount, vertexStart, vertexCount; };
struct MeshOutput {
std::vector<TempVertex> vertices;
std::vector<uint32_t> indices;
std::vector<PageRange> pages;
uint64_t hash = 0;
uint64_t bytes = 0;
uint64_t triangles = 0;
uint64_t ambiguousFaces = 0;
uint64_t multiCrossEdges = 0;
uint64_t missingAnalyticCrossings = 0;
uint64_t contourDegreeFaults = 0;
uint64_t openContourLoops = 0;
uint64_t explicitThinTriangles = 0;
uint64_t thinFeaturesDetected = 0;
uint64_t thinFeaturesHiddenInLattice = 0;
uint64_t thinFeaturesMeshed = 0;
uint64_t pageBorderVertices = 0;
uint64_t qefClampedVertices = 0;
uint64_t cellContourFaults = 0;
uint64_t cachedHermiteFallbacks = 0;
uint64_t nonManifoldEdges = 0;
uint64_t boundaryEdges = 0;
uint64_t boundaryThinEdges = 0;
uint64_t boundaryNearGridEdge = 0;
uint64_t degenerateTriangles = 0;
uint64_t overusedEdges = 0;
uint64_t displacedHash = 0;
double maxDisplacementMeters = 0.0;
};
struct Bin3D { std::vector<uint32_t> ids; };
struct BlendBox { int32_t lo[3]{}, hi[3]{}; };
struct ProjectionBins { std::vector<uint32_t> ids[kBucketCount * kBucketCount]; };
struct Corpus {
std::vector<Shape> shapes;
Bin3D bins3d[kBucketCount * kBucketCount * kBucketCount];
Bin3D blendBins3d[kBucketCount * kBucketCount * kBucketCount];
ProjectionBins projections[3];
std::vector<BlendBox> blendBoxes;
std::vector<uint8_t> blendNodeMask;
std::vector<uint32_t> blendNodeIndices;
uint64_t blendZoneNodes = 0;
uint64_t blendZoneCells = 0;
double blendZoneBuildMs = 0.0;
uint64_t seed = 0;
uint32_t maxMembershipAudit = 0;
};
struct WorkerLines {
std::vector<Crossing> crossings;
};
struct FarOutput {
std::vector<Vertex> vertices;
std::vector<uint32_t> indices;
std::vector<PageRange> patches;
std::array<uint64_t, 6> lodNodes{};
uint64_t nodes = 0, descendants = 0, patchCount = 0, bytes = 0, hash = 0;
};
double ms(Clock::time_point a, Clock::time_point b) {
return std::chrono::duration<double, std::milli>(b - a).count();
}
uint64_t mix64(uint64_t x) {
x += 0x9E3779B97F4A7C15ull;
x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9ull;
x = (x ^ (x >> 27)) * 0x94D049BB133111EBull;
return x ^ (x >> 31);
}
struct Rng {
uint64_t state;
explicit Rng(uint64_t s) : state(s) {}
uint32_t next() { state += 0x9E3779B97F4A7C15ull; return static_cast<uint32_t>(mix64(state) >> 32); }
int range(int lo, int hi) { return lo + static_cast<int>(next() % static_cast<uint32_t>(hi - lo + 1)); }
bool chance(int n, int d) { return static_cast<int>(next() % d) < n; }
};
int64_t round_div(int64_t n, int64_t d) {
if (n >= 0) return (n + d / 2) / d;
return -((-n + d / 2) / d);
}
int floor_div(int a, int b) {
const int q = a / b;
const int r = a % b;
return q - ((r != 0 && ((r < 0) != (b < 0))) ? 1 : 0);
}
int64_t floor_div64(int64_t a, int64_t b) {
const int64_t q = a / b;
const int64_t r = a % b;
return q - ((r != 0 && ((r < 0) != (b < 0))) ? 1 : 0);
}
int ceil_div(int a, int b) { return -floor_div(-a, b); }
void build_quadric(Shape& s, uint32_t rotationIndex) {
const auto& r = kRotations[rotationIndex % kRotations.size()];
for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) s.rot[i][j] = r.v[i][j];
// Q = R * diag(1/r^2) * R^T. Each term is rounded once to Q28.
for (int a = 0; a < 3; ++a) for (int b = 0; b < 3; ++b) {
int64_t sum = 0;
for (int k = 0; k < 3; ++k) {
const int64_t prod = static_cast<int64_t>(s.rot[a][k]) * s.rot[b][k];
const int64_t den = (int64_t{1} << 30) * s.radius[k] * s.radius[k];
sum += round_div(prod * kQScale, den);
}
s.q[a][b] = sum;
}
for (int axis = 0; axis < 3; ++axis) {
int64_t extentNumerator = 0;
for (int k = 0; k < 3; ++k) {
const int64_t v = std::abs(s.rot[axis][k]);
extentNumerator += v * s.radius[k];
}
const int32_t extent = static_cast<int32_t>((extentNumerator + 32767) / 32768) + 3;
s.lo[axis] = s.c[axis] - extent;
s.hi[axis] = s.c[axis] + extent;
}
}
void add_shape(Corpus& c, int x, int y, int z, int rx, int ry, int rz,
Operation op, FeatureKind kind, uint32_t rotation, bool auditThin = false) {
Shape s;
s.c[0] = x; s.c[1] = y; s.c[2] = z;
s.radius[0] = std::max(1, rx); s.radius[1] = std::max(1, ry); s.radius[2] = std::max(1, rz);
s.op = op; s.kind = kind; s.id = static_cast<uint32_t>(c.shapes.size()); s.auditThin = auditThin;
build_quadric(s, rotation);
c.shapes.push_back(s);
}
void add_sphere(Corpus& c, int x, int y, int z, int r, Operation op, FeatureKind kind) {
add_shape(c, x, y, z, r, r, r, op, kind, 0);
}
int64_t eval_quad(const Shape& s, const int64_t d[3]);
void build_blend_zones(Corpus& c) {
const int pad = (kBlendWidthCm + 1) / 2 + kCellStepCm;
c.blendBoxes.clear();
for (size_t i = 0; i < c.shapes.size(); ++i) {
const Shape& a = c.shapes[i];
if (a.kind == FeatureKind::Stratum) continue;
for (size_t j = i + 1; j < c.shapes.size(); ++j) {
const Shape& b = c.shapes[j];
if (b.kind == FeatureKind::Stratum) continue;
BlendBox box{};
bool intersects = true;
for (int axis = 0; axis < 3; ++axis) {
box.lo[axis] = std::max(a.lo[axis] - pad, b.lo[axis] - pad);
box.hi[axis] = std::min(a.hi[axis] + pad, b.hi[axis] + pad);
box.lo[axis] = std::max(box.lo[axis], kGridMinCm);
box.hi[axis] = std::min(box.hi[axis], kGridMaxCm);
intersects &= box.lo[axis] <= box.hi[axis];
}
if (intersects) c.blendBoxes.push_back(box);
}
}
const size_t rowCount = static_cast<size_t>(kNodes) * kNodes;
std::vector<std::array<uint64_t,kWordsPerLine>> nodeRows(rowCount);
auto setRange = [](auto& words, int lo, int hi) {
const int firstWord=lo>>6, lastWord=hi>>6;
for (int w=firstWord; w<=lastWord; ++w) {
const int firstBit=(w==firstWord)?(lo&63):0;
const int lastBit=(w==lastWord)?(hi&63):63;
const uint64_t low=~uint64_t{0}<<firstBit;
const uint64_t high=lastBit==63?~uint64_t{0}:((uint64_t{1}<<(lastBit+1))-1);
words[w] |= low & high;
}
};
for (const BlendBox& box : c.blendBoxes) {
int nlo[3], nhi[3];
for (int axis = 0; axis < 3; ++axis) {
nlo[axis] = std::clamp(ceil_div(box.lo[axis] - kGridMinCm, kCellStepCm), 0, kNodes - 1);
nhi[axis] = std::clamp(floor_div(box.hi[axis] - kGridMinCm, kCellStepCm), 0, kNodes - 1);
}
if (nlo[0] <= nhi[0] && nlo[1] <= nhi[1] && nlo[2] <= nhi[2])
for (int z = nlo[2]; z <= nhi[2]; ++z) for (int y = nlo[1]; y <= nhi[1]; ++y)
setRange(nodeRows[static_cast<size_t>(z) * kNodes + y], nlo[0], nhi[0]);
}
c.blendNodeMask.assign(static_cast<size_t>(kNodes) * kNodes * kNodes, 0);
c.blendNodeIndices.clear();
c.blendNodeIndices.reserve(300000);
c.blendZoneNodes = 0;
for (size_t row = 0; row < rowCount; ++row) {
const size_t base=row*kNodes;
for (int w=0; w<kWordsPerLine; ++w) {
uint64_t bits=nodeRows[row][w];
while (bits) {
const int x=w*64+static_cast<int>(std::countr_zero(bits)); bits&=bits-1;
if (x>=kNodes) continue;
const size_t index=base+x;
c.blendNodeMask[index]=1;
c.blendNodeIndices.push_back(static_cast<uint32_t>(index));
++c.blendZoneNodes;
}
}
}
c.blendZoneCells = 0;
}
void filter_blend_zone_mask(Corpus& c);
size_t node_mask_index(int x, int y, int z);
void build_corpus(Corpus& c, uint64_t seed) {
c.seed = seed;
Rng rng(seed ^ 0x0A57A20261002ull);
std::array<std::array<std::array<std::array<int, 3>, 3>, 3>, 2> centers{};
for (int layer = 0; layer < 2; ++layer) {
for (int iy = 0; iy < 3; ++iy) for (int ix = 0; ix < 3; ++ix) {
const int x = (ix - 1) * 1300 + rng.range(-170, 170);
const int y = (iy - 1) * 1300 + rng.range(-170, 170);
const int z = (layer == 0 ? -850 : 850) + rng.range(-120, 120);
centers[layer][iy][ix] = {x, y, z};
const int rx = rng.range(480, 820);
const int ry = rng.range(430, 760);
const int rz = rng.range(300, 540);
add_shape(c, x, y, z, rx, ry, rz, Operation::CarveAir, FeatureKind::Room,
rng.next() % 8);
}
}
// Curved tunnel runs are unions of overlapping analytic ellipsoidal segments.
for (int layer = 0; layer < 2; ++layer) {
for (int iy = 0; iy < 3; ++iy) for (int ix = 0; ix < 3; ++ix) {
const auto a = centers[layer][iy][ix];
for (int dir = 0; dir < 2; ++dir) {
const int nx = ix + (dir == 0);
const int ny = iy + (dir == 1);
if (nx >= 3 || ny >= 3) continue;
const auto b = centers[layer][ny][nx];
const int cx = (a[0] + b[0]) / 2 + rng.range(-220, 220);
const int cy = (a[1] + b[1]) / 2 + rng.range(-220, 220);
const int cz = (a[2] + b[2]) / 2 + rng.range(-150, 150);
for (int seg = 0; seg < 5; ++seg) {
const int t = 1 + seg;
const int x = (a[0] * (6 - t) + b[0] * t) / 6 + (seg == 2 ? cx - (a[0] + b[0]) / 2 : 0);
const int y = (a[1] * (6 - t) + b[1] * t) / 6 + (seg == 2 ? cy - (a[1] + b[1]) / 2 : 0);
const int z = (a[2] * (6 - t) + b[2] * t) / 6 + (seg == 2 ? cz - (a[2] + b[2]) / 2 : 0);
const int r = rng.range(210, 295);
// Adjacent ellipsoids use changing cross-sections and axis orientation to
// avoid a chain of identical spherical tunnel beads.
add_shape(c, x, y, z, r, r * rng.range(82, 115) / 100,
r * rng.range(78, 112) / 100, Operation::CarveAir,
FeatureKind::Tunnel, rng.next() % 8);
}
}
}
}
// Vertical connectors between stacked room layers.
for (int iy = 0; iy < 3; ++iy) for (int ix = 0; ix < 3; ++ix) {
const auto a = centers[0][iy][ix], b = centers[1][iy][ix];
for (int seg = 1; seg <= 3; ++seg) {
const int r = rng.range(195, 265);
add_shape(c, (a[0] * (4 - seg) + b[0] * seg) / 4,
(a[1] * (4 - seg) + b[1] * seg) / 4,
(a[2] * (4 - seg) + b[2] * seg) / 4,
r, r * rng.range(80, 115) / 100, r * rng.range(78, 112) / 100,
Operation::CarveAir, FeatureKind::Tunnel, rng.next() % 8);
}
}
// Dense constructive formations, placed in chamber space or near chamber walls.
for (int i = 0; i < 40; ++i) {
const int x = rng.range(-2500, 2500), y = rng.range(-2500, 2500);
const int z = rng.range(-1900, 1900);
add_shape(c, x, y, z, rng.range(70, 150), rng.range(70, 150), rng.range(260, 520),
Operation::AddRock, FeatureKind::Pillar, rng.next() % 8);
}
for (int i = 0; i < 36; ++i) {
const auto center = centers[i % 2][(i / 2) % 3][(i / 6) % 3];
const int sign = (i & 1) ? 1 : -1;
add_shape(c, center[0] + rng.range(-260, 260), center[1] + rng.range(-260, 260),
center[2] + sign * rng.range(260, 410), rng.range(45, 85),
rng.range(45, 85), rng.range(230, 370), Operation::AddRock,
(i & 1) ? FeatureKind::Stalagmite : FeatureKind::Stalactite, rng.next() % 8);
}
for (int i = 0; i < 42; ++i) {
const auto center = centers[i % 2][(i / 2) % 3][(i / 6) % 3];
add_shape(c, center[0] + rng.range(-350, 350), center[1] + rng.range(-350, 350),
center[2] + rng.range(-220, 220), rng.range(250, 650), rng.range(130, 320),
rng.range(30, 90), Operation::AddRock, FeatureKind::Shelf, rng.next() % 8);
}
for (int i = 0; i < 24; ++i) {
const auto center = centers[i % 2][(i / 2) % 3][(i / 6) % 3];
add_shape(c, center[0] + rng.range(-280, 280), center[1] + rng.range(-280, 280),
center[2] + rng.range(-250, 250), rng.range(180, 390), rng.range(150, 340),
rng.range(140, 330), Operation::AddRock, FeatureKind::FloatingMass, rng.next() % 8);
}
// Several crater cuts, including deliberately intersecting crater groups.
for (int i = 0; i < 12; ++i) {
const auto center = centers[i % 2][(i / 2) % 3][(i / 6) % 3];
const int x = center[0] + rng.range(300, 650);
const int y = center[1] + rng.range(-280, 280);
const int z = center[2] + rng.range(-280, 280);
add_shape(c, x, y, z, rng.range(230, 520), rng.range(210, 460), rng.range(180, 420),
Operation::CarveAir, FeatureKind::Crater, rng.next() % 8);
}
// Crowded junction: a bounded 40-shape overlap cluster exercises long event lists.
for (int i = 0; i < 40; ++i) {
const int x = rng.range(-110, 110), y = rng.range(-110, 110), z = rng.range(-110, 110);
const Operation op = (i % 3 == 0) ? Operation::AddRock : Operation::CarveAir;
const int r = rng.range(75, 190);
add_shape(c, x, y, z, r, rng.range(70, 200), rng.range(75, 185), op,
FeatureKind::CrowdedJunction, rng.next() % 8);
}
// Three widely separated, differently oriented rock bands replace the repeated thin stack.
auto snapToLattice = [](int value) {
const int node = static_cast<int>(std::llround((value - kGridMinCm) / static_cast<double>(kCellStepCm)));
return kGridMinCm + node * kCellStepCm;
};
auto isAirBeforeStratum = [&](int x, int y, int z) {
const int64_t p[3] = {static_cast<int64_t>(x)*kSubPerCm,
static_cast<int64_t>(y)*kSubPerCm,
static_cast<int64_t>(z)*kSubPerCm};
bool rock = true;
for (const Shape& s : c.shapes) {
bool insideAabb = true;
for (int a=0;a<3;++a) insideAabb &= p[a]>=static_cast<int64_t>(s.lo[a])*kSubPerCm
&& p[a]<=static_cast<int64_t>(s.hi[a])*kSubPerCm;
if (!insideAabb) continue;
const int64_t d[3] = {p[0]-static_cast<int64_t>(s.c[0])*kSubPerCm,
p[1]-static_cast<int64_t>(s.c[1])*kSubPerCm,
p[2]-static_cast<int64_t>(s.c[2])*kSubPerCm};
if (eval_quad(s,d) <= kQScale*kSubPerCm*kSubPerCm) rock = s.op==Operation::AddRock;
}
return !rock;
};
for (int i = 0; i < 3; ++i) {
const auto center = centers[i % 2][(i / 2) % 3][(i / 6) % 3];
const int desired[3] = {center[0] + rng.range(-180,180),center[1] + rng.range(-180,180),
center[2] + rng.range(-180,180)};
int placed[3] = {snapToLattice(desired[0]),snapToLattice(desired[1]),snapToLattice(desired[2])};
if (!isAirBeforeStratum(placed[0],placed[1],placed[2])) {
bool found=false;
for (int radius=25;radius<=300&&!found;radius+=25) {
const int offsets[6][3]={{radius,0,0},{-radius,0,0},{0,radius,0},{0,-radius,0},{0,0,radius},{0,0,-radius}};
for(const auto& offset:offsets){
int candidate[3]={snapToLattice(desired[0]+offset[0]),snapToLattice(desired[1]+offset[1]),
snapToLattice(desired[2]+offset[2])};
if(isAirBeforeStratum(candidate[0],candidate[1],candidate[2])){std::copy(candidate,candidate+3,placed);found=true;break;}
}
}
}
add_shape(c, placed[0], placed[1], placed[2], rng.range(300, 580), rng.range(250, 520),
rng.range(7, 12), Operation::AddRock, FeatureKind::Stratum,
std::array<uint32_t,3>{4,2,3}[i]);
}
// A guaranteed isolated sub-cell air seam in solid rock. No 25 cm lattice node lies inside it.
add_shape(c, 2900, -2900, 2887, 500, 400, 6, Operation::CarveAir,
FeatureKind::Stratum, 0, true);
const auto blendBuildBegin = Clock::now();
build_blend_zones(c);
c.blendZoneBuildMs = ms(blendBuildBegin, Clock::now());
// Build deterministic 3D and axis-projection bins in feature order.
for (uint32_t id = 0; id < c.shapes.size(); ++id) {
const Shape& s = c.shapes[id];
const int bx0 = std::clamp(floor_div(s.lo[0] - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int bx1 = std::clamp(floor_div(s.hi[0] - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int by0 = std::clamp(floor_div(s.lo[1] - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int by1 = std::clamp(floor_div(s.hi[1] - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int bz0 = std::clamp(floor_div(s.lo[2] - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int bz1 = std::clamp(floor_div(s.hi[2] - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
for (int bz = bz0; bz <= bz1; ++bz) for (int by = by0; by <= by1; ++by)
for (int bx = bx0; bx <= bx1; ++bx)
c.bins3d[(bz * kBucketCount + by) * kBucketCount + bx].ids.push_back(id);
const int blendLoX = std::clamp(floor_div(s.lo[0] - kBlendWidthCm - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int blendHiX = std::clamp(floor_div(s.hi[0] + kBlendWidthCm - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int blendLoY = std::clamp(floor_div(s.lo[1] - kBlendWidthCm - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int blendHiY = std::clamp(floor_div(s.hi[1] + kBlendWidthCm - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int blendLoZ = std::clamp(floor_div(s.lo[2] - kBlendWidthCm - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int blendHiZ = std::clamp(floor_div(s.hi[2] + kBlendWidthCm - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
for (int bz = blendLoZ; bz <= blendHiZ; ++bz) for (int by = blendLoY; by <= blendHiY; ++by)
for (int bx = blendLoX; bx <= blendHiX; ++bx)
c.blendBins3d[(bz * kBucketCount + by) * kBucketCount + bx].ids.push_back(id);
for (int axis = 0; axis < 3; ++axis) {
int a = (axis + 1) % 3, b = (axis + 2) % 3;
int a0 = std::clamp(floor_div(s.lo[a] - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
int a1 = std::clamp(floor_div(s.hi[a] - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
int b0 = std::clamp(floor_div(s.lo[b] - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
int b1 = std::clamp(floor_div(s.hi[b] - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
for (int ib = b0; ib <= b1; ++ib) for (int ia = a0; ia <= a1; ++ia)
c.projections[axis].ids[ib * kBucketCount + ia].push_back(id);
}
}
const auto zoneFilterBegin = Clock::now();
filter_blend_zone_mask(c);
c.blendZoneBuildMs += ms(zoneFilterBegin, Clock::now());
}
int bucket_of_sub(int64_t qSub) {
const int64_t qCmFloor = floor_div64(qSub, kSubPerCm);
return std::clamp(floor_div(static_cast<int>(qCmFloor) - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
}
int64_t eval_quad(const Shape& s, const int64_t d[3]) {
int64_t value = 0;
value += s.q[0][0] * d[0] * d[0];
value += s.q[1][1] * d[1] * d[1];
value += s.q[2][2] * d[2] * d[2];
value += 2 * s.q[0][1] * d[0] * d[1];
value += 2 * s.q[0][2] * d[0] * d[2];
value += 2 * s.q[1][2] * d[1] * d[2];
return value;
}
double shape_phi_approx(const Shape& s, const int64_t pSub[3]) {
int64_t d[3] = {pSub[0] - static_cast<int64_t>(s.c[0]) * kSubPerCm,
pSub[1] - static_cast<int64_t>(s.c[1]) * kSubPerCm,
pSub[2] - static_cast<int64_t>(s.c[2]) * kSubPerCm};
const int64_t q = eval_quad(s, d);
const double q2 = std::max(0.0, static_cast<double>(q)
/ static_cast<double>(kQScale * kSubPerCm * kSubPerCm));
const double radius = static_cast<double>(std::min({s.radius[0], s.radius[1], s.radius[2]}));
return (1.0 - std::sqrt(q2)) * radius;
}
void filter_blend_zone_mask(Corpus& c) {
c.blendZoneNodes = 0;
std::vector<uint32_t> candidates = std::move(c.blendNodeIndices);
c.blendNodeIndices.clear();
c.blendNodeIndices.reserve(candidates.size() / 4 + 1024);
for (uint32_t rawIndex : candidates) {
const size_t nodeIndex = rawIndex;
const int x = static_cast<int>(nodeIndex % kNodes);
const int y = static_cast<int>((nodeIndex / kNodes) % kNodes);
const int z = static_cast<int>(nodeIndex / (kNodes * kNodes));
uint8_t& marked = c.blendNodeMask[nodeIndex];
if (!marked) continue;
const int64_t p[3] = {
static_cast<int64_t>(kGridMinCm + x * kCellStepCm) * kSubPerCm,
static_cast<int64_t>(kGridMinCm + y * kCellStepCm) * kSubPerCm,
static_cast<int64_t>(kGridMinCm + z * kCellStepCm) * kSubPerCm};
const int bx = std::clamp(floor_div(kGridMinCm + x * kCellStepCm - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int by = std::clamp(floor_div(kGridMinCm + y * kCellStepCm - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int bz = std::clamp(floor_div(kGridMinCm + z * kCellStepCm - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
uint32_t nearSurfaces = 0;
const auto& candidates = c.blendBins3d[(bz * kBucketCount + by) * kBucketCount + bx].ids;
for (uint32_t id : candidates) {
const Shape& s = c.shapes[id];
if (s.kind == FeatureKind::Stratum) continue;
bool insideExpandedAabb=true;
for (int axis=0;axis<3;++axis)
insideExpandedAabb &= p[axis]>=static_cast<int64_t>(s.lo[axis]-kBlendWidthCm)*kSubPerCm
&& p[axis]<=static_cast<int64_t>(s.hi[axis]+kBlendWidthCm)*kSubPerCm;
if (!insideExpandedAabb) continue;
if (std::abs(shape_phi_approx(s, p)) <= 0.5 * kBlendWidthCm && ++nearSurfaces >= 2) break;
}
marked = nearSurfaces >= 2 ? 1 : 0;
c.blendZoneNodes += marked;
if (marked) c.blendNodeIndices.push_back(static_cast<uint32_t>(nodeIndex));
}
std::vector<uint8_t> cellMask(static_cast<size_t>(kCoreCells) * kCoreCells * kCoreCells, 0);
for (uint32_t nodeIndex : c.blendNodeIndices) {
const int x = static_cast<int>(nodeIndex % kNodes);
const int y = static_cast<int>((nodeIndex / kNodes) % kNodes);
const int z = static_cast<int>(nodeIndex / (kNodes * kNodes));
for (int cz = z - 1; cz <= z; ++cz) for (int cy = y - 1; cy <= y; ++cy) for (int cx = x - 1; cx <= x; ++cx) {
if (cx < kHalo || cy < kHalo || cz < kHalo
|| cx >= kHalo + kCoreCells || cy >= kHalo + kCoreCells || cz >= kHalo + kCoreCells) continue;
const size_t index = (static_cast<size_t>(cx - kHalo) * kCoreCells + (cy - kHalo)) * kCoreCells + (cz - kHalo);
cellMask[index] = 1;
}
}
c.blendZoneCells = std::count(cellMask.begin(), cellMask.end(), uint8_t{1});
}
bool contains_sub(const Shape& s, const int64_t pSub[3]) {
int64_t d[3] = {pSub[0] - static_cast<int64_t>(s.c[0]) * kSubPerCm,
pSub[1] - static_cast<int64_t>(s.c[1]) * kSubPerCm,
pSub[2] - static_cast<int64_t>(s.c[2]) * kSubPerCm};
return eval_quad(s, d) <= kQScale * kSubPerCm * kSubPerCm;
}
bool shape_aabb_contains_sub(const Shape& s, const int64_t pSub[3]) {
for (int a = 0; a < 3; ++a) {
if (pSub[a] < static_cast<int64_t>(s.lo[a]) * kSubPerCm
|| pSub[a] > static_cast<int64_t>(s.hi[a]) * kSubPerCm) return false;
}
return true;
}
bool point_rock(const Corpus& c, const int64_t pSub[3]) {
const int xcm = static_cast<int>(floor_div(static_cast<int>(pSub[0]), static_cast<int>(kSubPerCm)));
const int ycm = static_cast<int>(floor_div(static_cast<int>(pSub[1]), static_cast<int>(kSubPerCm)));
const int zcm = static_cast<int>(floor_div(static_cast<int>(pSub[2]), static_cast<int>(kSubPerCm)));
const int bx = std::clamp(floor_div(xcm - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int by = std::clamp(floor_div(ycm - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int bz = std::clamp(floor_div(zcm - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
bool rock = true;
const auto& bucket = c.bins3d[(bz * kBucketCount + by) * kBucketCount + bx].ids;
for (uint32_t id : bucket) {
const Shape& s = c.shapes[id];
if (shape_aabb_contains_sub(s, pSub) && contains_sub(s, pSub)) rock = (s.op == Operation::AddRock);
}
return rock;
}
double smooth_min(double a, double b, double k) {
const double h = std::max(k - std::abs(a - b), 0.0) / k;
return std::min(a, b) - h * h * k * 0.25;
}
double smooth_max(double a, double b, double k) {
return -smooth_min(-a, -b, k);
}
double blended_field(const Corpus& c, const int64_t pSub[3]) {
const int xcm = static_cast<int>(floor_div(static_cast<int>(pSub[0]), static_cast<int>(kSubPerCm)));
const int ycm = static_cast<int>(floor_div(static_cast<int>(pSub[1]), static_cast<int>(kSubPerCm)));
const int zcm = static_cast<int>(floor_div(static_cast<int>(pSub[2]), static_cast<int>(kSubPerCm)));
const int bx = std::clamp(floor_div(xcm - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int by = std::clamp(floor_div(ycm - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int bz = std::clamp(floor_div(zcm - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const auto& bucket = c.blendBins3d[(bz * kBucketCount + by) * kBucketCount + bx].ids;
double field = 100000.0; // positive means rock; the untouched world is solid rock
for (uint32_t id : bucket) {
const Shape& s = c.shapes[id];
bool insideExpandedAabb = true;
for (int axis = 0; axis < 3; ++axis)
insideExpandedAabb &= pSub[axis] >= static_cast<int64_t>(s.lo[axis] - kBlendWidthCm) * kSubPerCm
&& pSub[axis] <= static_cast<int64_t>(s.hi[axis] + kBlendWidthCm) * kSubPerCm;
if (!insideExpandedAabb) continue;
int64_t d[3] = {pSub[0] - static_cast<int64_t>(s.c[0]) * kSubPerCm,
pSub[1] - static_cast<int64_t>(s.c[1]) * kSubPerCm,
pSub[2] - static_cast<int64_t>(s.c[2]) * kSubPerCm};
const double q2 = std::max(0.0, static_cast<double>(eval_quad(s, d))
/ static_cast<double>(kQScale * kSubPerCm * kSubPerCm));
const double radius = static_cast<double>(std::min({s.radius[0], s.radius[1], s.radius[2]}));
double phi = (1.0 - std::sqrt(q2)) * radius;
if (eval_quad(s, d) == kQScale * kSubPerCm * kSubPerCm) phi = 1.0e-8;
if (s.kind == FeatureKind::Stratum) {
// The few retained thin bands keep hard, exact CSG so their sub-cell roots survive.
field = s.op == Operation::AddRock ? std::max(field, phi) : std::min(field, -phi);
} else {
field = s.op == Operation::AddRock
? smooth_max(field, phi, static_cast<double>(kBlendWidthCm))
: smooth_min(field, -phi, static_cast<double>(kBlendWidthCm));
}
}
return field;
}
void blended_field_gradient(const Corpus& c, const int64_t pSub[3], double& field, double gradient[3]) {
const int xcm = static_cast<int>(floor_div(static_cast<int>(pSub[0]), static_cast<int>(kSubPerCm)));
const int ycm = static_cast<int>(floor_div(static_cast<int>(pSub[1]), static_cast<int>(kSubPerCm)));
const int zcm = static_cast<int>(floor_div(static_cast<int>(pSub[2]), static_cast<int>(kSubPerCm)));
const int bx = std::clamp(floor_div(xcm-kGridMinCm,kBucketCm),0,kBucketCount-1);
const int by = std::clamp(floor_div(ycm-kGridMinCm,kBucketCm),0,kBucketCount-1);
const int bz = std::clamp(floor_div(zcm-kGridMinCm,kBucketCm),0,kBucketCount-1);
const auto& bucket=c.blendBins3d[(bz*kBucketCount+by)*kBucketCount+bx].ids;
field=100000.0; gradient[0]=gradient[1]=gradient[2]=0.0;
for (uint32_t id : bucket) {
const Shape& s=c.shapes[id];
bool inside=true;
for (int a=0;a<3;++a) inside &= pSub[a]>=static_cast<int64_t>(s.lo[a]-kBlendWidthCm)*kSubPerCm
&& pSub[a]<=static_cast<int64_t>(s.hi[a]+kBlendWidthCm)*kSubPerCm;
if (!inside) continue;
const double d[3] = {pSub[0]/static_cast<double>(kSubPerCm)-s.c[0],
pSub[1]/static_cast<double>(kSubPerCm)-s.c[1],
pSub[2]/static_cast<double>(kSubPerCm)-s.c[2]};
int64_t dSub[3] = {pSub[0]-static_cast<int64_t>(s.c[0])*kSubPerCm,
pSub[1]-static_cast<int64_t>(s.c[1])*kSubPerCm,
pSub[2]-static_cast<int64_t>(s.c[2])*kSubPerCm};
const double q2=std::max(0.0,static_cast<double>(eval_quad(s,dSub))/static_cast<double>(kQScale*kSubPerCm*kSubPerCm));
const double root=std::sqrt(q2);
const double radius=static_cast<double>(std::min({s.radius[0],s.radius[1],s.radius[2]}));
double phi=(1.0-root)*radius;
double phiGrad[3]{};
if (root>1.0e-12) for (int i=0;i<3;++i) {
double qd=0.0;
for (int j=0;j<3;++j) qd += (static_cast<double>(s.q[i][j])/kQScale)*d[j];
phiGrad[i]=-radius*qd/root;
}
if (eval_quad(s,dSub)==kQScale*kSubPerCm*kSubPerCm) phi=1.0e-8;
double b=phi, bg[3]={phiGrad[0],phiGrad[1],phiGrad[2]};
const bool isMin=s.op==Operation::CarveAir;
if (isMin) { b=-b; for (double& v:bg) v=-v; }
if (s.kind==FeatureKind::Stratum) {
const bool take = isMin ? b<field : b>field;
if (take) { field=b; for (int a=0;a<3;++a) gradient[a]=bg[a]; }
continue;
}
const double k=static_cast<double>(kBlendWidthCm);
const double weightA = isMin
? std::clamp(0.5+(b-field)/(2.0*k),0.0,1.0)
: std::clamp(0.5+(field-b)/(2.0*k),0.0,1.0);
const double delta=std::abs(field-b);
if (delta>=k) {
const bool take = isMin ? b<field : b>field;
if (take) { field=b; for (int a=0;a<3;++a) gradient[a]=bg[a]; }
} else {
field=weightA*field+(1.0-weightA)*b-k*weightA*(1.0-weightA);
for (int a=0;a<3;++a) gradient[a]=weightA*gradient[a]+(1.0-weightA)*bg[a];
}
}
}
size_t node_mask_index(int x, int y, int z) {
return (static_cast<size_t>(z) * kNodes + y) * kNodes + x;
}
bool point_in_blend_zone(const Corpus& c, const int64_t pSub[3]) {
const int pad = (kBlendWidthCm + 1) / 2 + kCellStepCm;
uint32_t hits = 0;
for (const Shape& s : c.shapes) {
if (s.kind == FeatureKind::Stratum) continue;
bool inside = true;
for (int axis = 0; axis < 3; ++axis)
inside &= pSub[axis] >= static_cast<int64_t>(s.lo[axis] - pad) * kSubPerCm
&& pSub[axis] <= static_cast<int64_t>(s.hi[axis] + pad) * kSubPerCm;
if (inside && ++hits >= 2) return true;
}
return false;
}
bool point_rock_reference(const Corpus& c, const int64_t pSub[3], bool blendZone) {
return blendZone ? blended_field(c, pSub) >= 0.0 : point_rock(c, pSub);
}
bool point_rock_reference(const Corpus& c, const int64_t pSub[3]) {
return point_rock_reference(c, pSub, point_in_blend_zone(c, pSub));
}
double hard_field(const Corpus& c, const int64_t pSub[3]) {
const int xcm = static_cast<int>(floor_div(static_cast<int>(pSub[0]), static_cast<int>(kSubPerCm)));
const int ycm = static_cast<int>(floor_div(static_cast<int>(pSub[1]), static_cast<int>(kSubPerCm)));
const int zcm = static_cast<int>(floor_div(static_cast<int>(pSub[2]), static_cast<int>(kSubPerCm)));
const int bx = std::clamp(floor_div(xcm - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int by = std::clamp(floor_div(ycm - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int bz = std::clamp(floor_div(zcm - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const auto& bucket = c.blendBins3d[(bz * kBucketCount + by) * kBucketCount + bx].ids;
double field = 100000.0;
for (uint32_t id : bucket) {
const Shape& s = c.shapes[id];
bool insideExpandedAabb = true;
for (int axis = 0; axis < 3; ++axis)
insideExpandedAabb &= pSub[axis] >= static_cast<int64_t>(s.lo[axis] - kBlendWidthCm) * kSubPerCm
&& pSub[axis] <= static_cast<int64_t>(s.hi[axis] + kBlendWidthCm) * kSubPerCm;
if (!insideExpandedAabb) continue;
const double phi = shape_phi_approx(s, pSub);
field = s.op == Operation::AddRock ? std::max(field, phi) : std::min(field, -phi);
}
const bool exactRock = point_rock(c, pSub);
if ((field >= 0.0) != exactRock) {
const double magnitude = std::max(std::abs(field), 1.0e-8);
field = exactRock ? magnitude : -magnitude;
}
return field;
}
struct Interval { int64_t loSub, hiSub; int loSample, hiSample; };
int64_t eval_line(const Shape& s, int axis, int64_t variableSub,
const int64_t fixedSub[3]) {
int64_t p[3] = {fixedSub[0], fixedSub[1], fixedSub[2]};
p[axis] = variableSub;
int64_t d[3] = {p[0] - static_cast<int64_t>(s.c[0]) * kSubPerCm,
p[1] - static_cast<int64_t>(s.c[1]) * kSubPerCm,
p[2] - static_cast<int64_t>(s.c[2]) * kSubPerCm};
return eval_quad(s, d) - kQScale * kSubPerCm * kSubPerCm;
}
bool solve_shape_line(const Shape& s, int axis, const int64_t fixedSub[3], Interval& out) {
const int a = axis;
const int b = (axis + 1) % 3;
const int dAxis = (axis + 2) % 3;
const int64_t da = fixedSub[b] - static_cast<int64_t>(s.c[b]) * kSubPerCm;
const int64_t db = fixedSub[dAxis] - static_cast<int64_t>(s.c[dAxis]) * kSubPerCm;
const int64_t A = s.q[a][a];
const int64_t B = 2 * (s.q[a][b] * da + s.q[a][dAxis] * db);
if (A <= 0) return false;
const int64_t vertexFloor = static_cast<int64_t>(s.c[axis]) * kSubPerCm
+ floor_div64(-B, 2 * A);
const int64_t minSub = static_cast<int64_t>(s.lo[a]) * kSubPerCm;
const int64_t maxSub = static_cast<int64_t>(s.hi[a]) * kSubPerCm;
int64_t center = std::clamp<int64_t>(vertexFloor, minSub, maxSub);
int64_t best = center;
int64_t bestValue = eval_line(s, axis, center, fixedSub);
for (int64_t q = center - 1; q <= center + 1; ++q) {
if (q < minSub || q > maxSub) continue;
const int64_t v = eval_line(s, axis, q, fixedSub);
if (v < bestValue) { bestValue = v; best = q; }
}
if (bestValue > 0) return false; // exact integer predicate; no floating sign decision
int64_t lo = minSub, hi = best;
while (lo < hi) {
const int64_t mid = lo + (hi - lo) / 2;
if (eval_line(s, axis, mid, fixedSub) <= 0) hi = mid;
else lo = mid + 1;
}
const int64_t first = lo;
lo = best; hi = maxSub;
while (lo < hi) {
const int64_t mid = lo + (hi - lo + 1) / 2;
if (eval_line(s, axis, mid, fixedSub) <= 0) lo = mid;
else hi = mid - 1;
}
const int64_t last = lo;
if (eval_line(s, axis, first, fixedSub) > 0 || eval_line(s, axis, last, fixedSub) > 0) return false;
const int64_t baseSub = static_cast<int64_t>(kGridMinCm) * kSubPerCm;
const int64_t stepSub = static_cast<int64_t>(kCellStepCm) * kSubPerCm;
int firstSample = static_cast<int>(ceil_div(static_cast<int>(first - baseSub), static_cast<int>(stepSub)));
int lastSample = static_cast<int>(floor_div(static_cast<int>(last - baseSub), static_cast<int>(stepSub)));
firstSample = std::max(firstSample, 0);
lastSample = std::min(lastSample, kNodes - 1);
if (firstSample <= lastSample) {
const int64_t firstP = baseSub + firstSample * stepSub;
if (eval_line(s, axis, firstP, fixedSub) > 0) ++firstSample;
if (firstSample <= lastSample && eval_line(s, axis, baseSub + lastSample * stepSub, fixedSub) > 0) --lastSample;
} else { firstSample = 1; lastSample = 0; }
out = {first, last, firstSample, lastSample};
return true;
}
uint64_t compose_membership(const Corpus& c, const std::array<uint32_t, kShapeLimitPerLine>& ids,
uint32_t count, uint64_t mask) {
bool rock = true;
while (mask) {
const unsigned bit = std::countr_zero(mask);
const uint64_t b = uint64_t{1} << bit;
if (bit < count) rock = (c.shapes[ids[bit]].op == Operation::AddRock);
mask &= ~b;
}
return rock ? 1 : 0;
}
void set_word_run(uint64_t* words, int begin, int end, bool rock) {
if (!rock || begin >= end) return;
int firstWord = begin >> 6;
int lastWord = (end - 1) >> 6;
const int firstBit = begin & 63;
const int lastBit = (end - 1) & 63;
if (firstWord == lastWord) {
const uint64_t low = kMask64 << firstBit;
const uint64_t high = (lastBit == 63) ? kMask64 : ((uint64_t{1} << (lastBit + 1)) - 1);
words[firstWord] |= low & high;
return;
}
words[firstWord] |= kMask64 << firstBit;
for (int w = firstWord + 1; w < lastWord; ++w) words[w] = kMask64;
words[lastWord] |= (lastBit == 63) ? kMask64 : ((uint64_t{1} << (lastBit + 1)) - 1);
}
void parallel_ranges(size_t count, int workers, const std::function<void(int,size_t,size_t)>& fn) {
std::vector<std::thread> pool;
pool.reserve(workers > 1 ? workers : 0);
const size_t chunk = (count + static_cast<size_t>(workers) - 1) / static_cast<size_t>(workers);
for (int w = 0; w < workers; ++w) {
const size_t begin = std::min(count, static_cast<size_t>(w) * chunk);
const size_t end = std::min(count, begin + chunk);
if (workers == 1) { fn(w, begin, end); break; }
pool.emplace_back([&, w, begin, end] { fn(w, begin, end); });
}
for (auto& t : pool) t.join();
}
uint32_t line_id(int axis, int x, int y, int z) {
if (axis == 0) return static_cast<uint32_t>(z * kNodes + y); // X varies
if (axis == 1) return static_cast<uint32_t>(x * kNodes + z); // Y varies
return static_cast<uint32_t>(y * kNodes + x); // Z varies
}
void decode_line(int axis, uint32_t line, int& a, int& b) {
(void)axis;
a = static_cast<int>(line % kNodes);
b = static_cast<int>(line / kNodes);
// axis 0: a=y,b=z; axis 1: a=x,b=z; axis 2: a=x,b=y.
}
AxisData solve_axis(const Corpus& c, int axis, int workers, AxisStats& stats, bool& overflow) {
AxisData result;
const int lineCount = kNodes * kNodes;
result.signs.assign(static_cast<size_t>(lineCount) * kWordsPerLine, 0);
result.lines.resize(lineCount);
std::vector<WorkerLines> local(static_cast<size_t>(workers));
std::atomic<bool> lineOverflow{false};
std::vector<std::vector<uint16_t>> candidateStats(workers), intervalStats(workers), crossingStats(workers), runStats(workers);
const auto& bins = c.projections[axis];
const int axis1 = (axis + 1) % 3, axis2 = (axis + 2) % 3;
const int64_t domainMinSub = static_cast<int64_t>(kGridMinCm) * kSubPerCm;
const int64_t domainMaxSub = static_cast<int64_t>(kGridMaxCm) * kSubPerCm;
parallel_ranges(lineCount, workers, [&](int worker, size_t begin, size_t end) {
auto& outCross = local[worker].crossings;
auto& cstats = candidateStats[worker];
auto& istats = intervalStats[worker];
auto& xstats = crossingStats[worker];
auto& rstats = runStats[worker];
cstats.reserve(end - begin); istats.reserve(end - begin);
xstats.reserve(end - begin); rstats.reserve(end - begin);
std::array<uint32_t, kShapeLimitPerLine> ids{};
std::array<Interval, kShapeLimitPerLine> intervals{};
std::array<uint8_t, kShapeLimitPerLine> valid{};
std::array<uint64_t, kNodes + 1> enters{}, exits{};
std::array<uint64_t, kWordsPerLine> lineWords{};
struct Ev { int64_t q; uint8_t bit; uint8_t enter; };
std::array<Ev, kShapeLimitPerLine * 2> events{};
for (size_t lineIndex = begin; lineIndex < end; ++lineIndex) {
int p0, p1; decode_line(axis, static_cast<uint32_t>(lineIndex), p0, p1);
std::array<int, 3> localCm{};
localCm[axis1] = kGridMinCm + p0 * kCellStepCm;
localCm[axis2] = kGridMinCm + p1 * kCellStepCm;
const int b0 = std::clamp(floor_div(localCm[axis1] - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const int b1 = std::clamp(floor_div(localCm[axis2] - kGridMinCm, kBucketCm), 0, kBucketCount - 1);
const auto& bucket = bins.ids[b1 * kBucketCount + b0];
uint32_t candidateCount = 0;
for (uint32_t id : bucket) {
const Shape& s = c.shapes[id];
if (localCm[axis1] < s.lo[axis1] || localCm[axis1] > s.hi[axis1]
|| localCm[axis2] < s.lo[axis2] || localCm[axis2] > s.hi[axis2]
|| s.hi[axis] < kGridMinCm || s.lo[axis] > kGridMaxCm) continue;
if (candidateCount < kShapeLimitPerLine) ids[candidateCount++] = id;
else { lineOverflow.store(true, std::memory_order_relaxed); }
}
if (candidateCount > kShapeLimitPerLine) candidateCount = kShapeLimitPerLine;
cstats.push_back(static_cast<uint16_t>(candidateCount));
std::fill(enters.begin(), enters.end(), 0);
std::fill(exits.begin(), exits.end(), 0);
std::fill(lineWords.begin(), lineWords.end(), 0);
std::fill(valid.begin(), valid.end(), 0);
uint32_t intervalCount = 0;
uint32_t eventCount = 0;
int64_t fixedSub[3] = {0,0,0};
fixedSub[axis1] = static_cast<int64_t>(localCm[axis1]) * kSubPerCm;
fixedSub[axis2] = static_cast<int64_t>(localCm[axis2]) * kSubPerCm;
for (uint32_t i = 0; i < candidateCount; ++i) {
const Shape& s = c.shapes[ids[i]];
if (!solve_shape_line(s, axis, fixedSub, intervals[i])) continue;
valid[i] = 1;
++intervalCount;
const Interval& in = intervals[i];
if (in.loSample <= in.hiSample) {
enters[in.loSample] |= uint64_t{1} << i;
exits[in.hiSample + 1] |= uint64_t{1} << i;
}
if (in.loSub <= domainMinSub && in.hiSub >= domainMinSub) {
// The line begins inside this canonical interval; represented by an entry at index 0.
enters[0] |= uint64_t{1} << i;
}
if (in.loSub > domainMinSub && in.loSub <= domainMaxSub)
events[eventCount++] = {in.loSub, static_cast<uint8_t>(i), 1};
const int64_t exitQ = in.hiSub + 1;
if (exitQ > domainMinSub && exitQ <= domainMaxSub + 1)
events[eventCount++] = {exitQ, static_cast<uint8_t>(i), 0};
}
istats.push_back(static_cast<uint16_t>(intervalCount));
uint64_t active = 0;
// Membership at the first sample is exact; interval endpoints are canonical integers.
for (uint32_t i = 0; i < candidateCount; ++i) {
if (!valid[i]) continue;
const Interval& in = intervals[i];
if (in.loSub <= domainMinSub && in.hiSub >= domainMinSub)
active |= uint64_t{1} << i;
}
bool rock = compose_membership(c, ids, candidateCount, active) != 0;
int runBegin = 0;
uint32_t runCount = 1;
for (int k = 0; k <= kNodes; ++k) {
const uint64_t before = active;
active |= enters[k];
active &= ~exits[k];
if (k == 0) {
rock = compose_membership(c, ids, candidateCount, active) != 0;
continue;
}
if (before != active) {
const bool nextRock = compose_membership(c, ids, candidateCount, active) != 0;
if (nextRock != rock) {
set_word_run(lineWords.data(), runBegin, k, rock);
runBegin = k;
rock = nextRock;
++runCount;
}
}
}
set_word_run(lineWords.data(), runBegin, kNodes, rock);
std::copy(lineWords.begin(), lineWords.end(),
result.signs.begin() + static_cast<size_t>(lineIndex) * kWordsPerLine);
rstats.push_back(static_cast<uint16_t>(runCount));
std::sort(events.begin(), events.begin() + eventCount,
[](const Ev& a, const Ev& b) { return a.q < b.q || (a.q == b.q && a.bit < b.bit); });
const size_t lineCrossStart = outCross.size();
uint64_t continuousActive = 0;
for (uint32_t i = 0; i < candidateCount; ++i) {
if (!valid[i]) continue;
const Interval& in = intervals[i];
if (in.loSub <= domainMinSub && in.hiSub >= domainMinSub)
continuousActive |= uint64_t{1} << i;
}
for (uint32_t e = 0; e < eventCount;) {
uint32_t next = e + 1;
while (next < eventCount && events[next].q == events[e].q) ++next;
const bool beforeRock = compose_membership(c, ids, candidateCount, continuousActive) != 0;
for (uint32_t j = e; j < next; ++j) {
const uint64_t bit = uint64_t{1} << events[j].bit;
if (events[j].enter) continuousActive |= bit;
else continuousActive &= ~bit;
}
const bool afterRock = compose_membership(c, ids, candidateCount, continuousActive) != 0;
if (beforeRock != afterRock) {
uint32_t feature = ids[events[e].bit];
for (uint32_t j = e; j < next; ++j) feature = std::min(feature, ids[events[j].bit]);
const int64_t p32 = events[e].q * 2 - 1;
if (p32 >= static_cast<int64_t>(kGridMinCm) * 32
&& p32 <= static_cast<int64_t>(kGridMaxCm) * 32
&& outCross.size() < std::numeric_limits<uint32_t>::max())
outCross.push_back({static_cast<int32_t>(p32), feature,
static_cast<uint8_t>(beforeRock), static_cast<uint8_t>(afterRock)});
}
e = next;
}
const size_t lineCrossCount = outCross.size() - lineCrossStart;
xstats.push_back(static_cast<uint16_t>(std::min<size_t>(lineCrossCount, 65535)));
result.lines[lineIndex].candidates = static_cast<uint16_t>(candidateCount);
result.lines[lineIndex].primitiveIntervals = static_cast<uint16_t>(intervalCount);
result.lines[lineIndex].occupancyRuns = static_cast<uint16_t>(runCount);
result.lines[lineIndex].count = static_cast<uint16_t>(std::min<size_t>(lineCrossCount, 65535));
}
});
overflow = overflow || lineOverflow.load(std::memory_order_relaxed);
// Concatenate disjoint worker event streams in line order; each line range gets stable offsets.
size_t totalCross = 0;
std::vector<size_t> workerBase(static_cast<size_t>(workers));
for (int w = 0; w < workers; ++w) { workerBase[w] = totalCross; totalCross += local[w].crossings.size(); }
result.crossings.resize(totalCross);
const size_t chunk = (static_cast<size_t>(lineCount) + workers - 1) / workers;
for (int w = 0; w < workers; ++w) {
const size_t begin = std::min(static_cast<size_t>(lineCount), static_cast<size_t>(w) * chunk);
const size_t end = std::min(static_cast<size_t>(lineCount), begin + chunk);
size_t offset = workerBase[w];
// During the worker pass, crossing records for a line are appended sequentially.
for (size_t li = begin; li < end; ++li) {
LineRange& lr = result.lines[li];
lr.offset = static_cast<uint32_t>(offset);
offset += lr.count;
}
std::copy(local[w].crossings.begin(), local[w].crossings.end(), result.crossings.begin() + workerBase[w]);
}
// The stats vectors were produced in worker-range order; collect and summarize them.
auto summarize = [](std::vector<uint16_t> values, double& p50, double& p95, uint32_t& vmax, uint64_t& total) {
if (values.empty()) return;
std::sort(values.begin(), values.end());
total = std::accumulate(values.begin(), values.end(), uint64_t{0});
vmax = values.back();
const size_t i50 = static_cast<size_t>(0.50 * (values.size() - 1));
const size_t i95 = static_cast<size_t>(0.95 * (values.size() - 1));
p50 = values[i50]; p95 = values[i95];
};
std::vector<uint16_t> cv, iv, xv, rv;
cv.reserve(lineCount); iv.reserve(lineCount); xv.reserve(lineCount); rv.reserve(lineCount);
for (const auto& v : candidateStats) cv.insert(cv.end(), v.begin(), v.end());
for (const auto& v : intervalStats) iv.insert(iv.end(), v.begin(), v.end());
for (const auto& v : crossingStats) xv.insert(xv.end(), v.begin(), v.end());
for (const auto& v : runStats) rv.insert(rv.end(), v.begin(), v.end());
summarize(std::move(cv), stats.candidateP50, stats.candidateP95, stats.candidateMax, stats.candidateTotal);
summarize(std::move(iv), stats.intervalP50, stats.intervalP95, stats.intervalMax, stats.intervalTotal);
summarize(std::move(xv), stats.crossingP50, stats.crossingP95, stats.crossingMax, stats.crossingTotal);
summarize(std::move(rv), stats.runsP50, stats.runsP95, stats.runsMax, stats.runsTotal);
stats.intervalMax = std::max<uint32_t>(stats.intervalMax, 0);
return result;
}
uint64_t get_sign(const AxisData& axis, uint32_t line, int sample) {
return (axis.signs[static_cast<size_t>(line) * kWordsPerLine + (sample >> 6)] >> (sample & 63)) & 1;
}
uint64_t extract64(const AxisData& a, uint32_t line, int bit) {
const uint64_t* words = a.signs.data() + static_cast<size_t>(line) * kWordsPerLine;
const int wi = bit >> 6, shift = bit & 63;
uint64_t v = words[wi] >> shift;
if (shift != 0 && wi + 1 < kWordsPerLine) v |= words[wi + 1] << (64 - shift);
return v;
}
struct BlendStats {
uint64_t nodeEvaluations = 0;
uint64_t edgeSegments = 0;
uint64_t blendedCrossings = 0;
uint64_t boundarySignDisagreements = 0;
};
void apply_blend_zones(const Corpus& c, AxisData axes[3], AxisStats stats[3], BlendStats& out) {
const int64_t baseSub = static_cast<int64_t>(kGridMinCm) * kSubPerCm;
const int64_t stepSub = static_cast<int64_t>(kCellStepCm) * kSubPerCm;
std::vector<uint8_t> blendSigns(c.blendNodeMask.size(), 0);
std::array<std::vector<uint8_t>,3> affectedLines;
for (auto& v : affectedLines) v.assign(static_cast<size_t>(kNodes) * kNodes, 0);
for (uint32_t rawIndex : c.blendNodeIndices) {
const size_t index = rawIndex;
const int x = static_cast<int>(rawIndex % kNodes);
const int y = static_cast<int>((rawIndex / kNodes) % kNodes);
const int z = static_cast<int>(rawIndex / (kNodes * kNodes));
const int64_t p[3] = {
static_cast<int64_t>(kGridMinCm + x * kCellStepCm) * kSubPerCm,
static_cast<int64_t>(kGridMinCm + y * kCellStepCm) * kSubPerCm,
static_cast<int64_t>(kGridMinCm + z * kCellStepCm) * kSubPerCm};
blendSigns[index] = blended_field(c, p) >= 0.0;
for (int axis = 0; axis < 3; ++axis) affectedLines[axis][line_id(axis, x, y, z)] = 1;
++out.nodeEvaluations;
}
for (int axis = 0; axis < 3; ++axis) {
AxisData& data = axes[axis];
std::vector<Crossing> oldCrossings = std::move(data.crossings);
std::vector<Crossing> rebuilt;
rebuilt.reserve(oldCrossings.size() + 4096);
std::vector<uint16_t> counts(data.lines.size(), 0);
for (uint32_t li = 0; li < data.lines.size(); ++li) {
LineRange& line = data.lines[li];
const uint32_t oldOffset = line.offset, oldCount = line.count;
line.offset = static_cast<uint32_t>(rebuilt.size());
if (!affectedLines[axis][li]) {
rebuilt.insert(rebuilt.end(), oldCrossings.begin() + oldOffset,
oldCrossings.begin() + oldOffset + oldCount);
counts[li] = line.count;
continue;
}
int p0, p1; decode_line(axis, li, p0, p1);
const int a1 = (axis + 1) % 3, a2 = (axis + 2) % 3;
int node[3]{}; node[a1] = p0; node[a2] = p1;
bool zone[kNodes]{}, signs[kNodes]{};
for (int sample = 0; sample < kNodes; ++sample) {
node[axis] = sample;
zone[sample] = c.blendNodeMask[node_mask_index(node[0], node[1], node[2])] != 0;
signs[sample] = get_sign(data, li, sample) != 0;
if (zone[sample]) {
signs[sample] = blendSigns[node_mask_index(node[0], node[1], node[2])] != 0;
}
uint64_t& word = data.signs[static_cast<size_t>(li) * kWordsPerLine + (sample >> 6)];
const uint64_t bit = uint64_t{1} << (sample & 63);
if (signs[sample]) word |= bit; else word &= ~bit;
}
uint32_t oldIndex = 0;
for (int edge = 0; edge + 1 < kNodes; ++edge) {
const int64_t startSub = baseSub + edge * stepSub;
const int64_t endSub = startSub + stepSub;
const int32_t start32 = static_cast<int32_t>(startSub * 2);
const int32_t end32 = static_cast<int32_t>(endSub * 2);
const bool edgeZone = zone[edge] || zone[edge + 1];
if (edgeZone) ++out.edgeSegments;
while (oldIndex < oldCount
&& oldCrossings[oldOffset + oldIndex].positionSub32 <= start32) {
++oldIndex;
}
uint32_t scan = oldIndex;
for (; scan < oldCount && oldCrossings[oldOffset + scan].positionSub32 < end32; ++scan) {
const Crossing& old = oldCrossings[oldOffset + scan];
if (!edgeZone) rebuilt.push_back(old);
else if (old.shapeId < c.shapes.size()
&& c.shapes[old.shapeId].kind == FeatureKind::Stratum) rebuilt.push_back(old);
}
oldIndex = scan;
if (!edgeZone || signs[edge] == signs[edge + 1]) continue;
int64_t pLeft[3] = {
static_cast<int64_t>(kGridMinCm + node[0] * kCellStepCm) * kSubPerCm,
static_cast<int64_t>(kGridMinCm + node[1] * kCellStepCm) * kSubPerCm,
static_cast<int64_t>(kGridMinCm + node[2] * kCellStepCm) * kSubPerCm};
int64_t pRight[3] = {pLeft[0], pLeft[1], pLeft[2]};
pLeft[axis] = startSub; pRight[axis] = endSub;
const double leftValue = zone[edge] ? blended_field(c, pLeft) : hard_field(c, pLeft);
const double rightValue = zone[edge + 1] ? blended_field(c, pRight) : hard_field(c, pRight);
const bool leftFieldRock = leftValue >= 0.0;
const bool rightFieldRock = rightValue >= 0.0;
if (leftFieldRock != signs[edge] || rightFieldRock != signs[edge + 1])
++out.boundarySignDisagreements;
if (leftFieldRock == rightFieldRock) continue;
const bool beforeRock = leftFieldRock;
double rootSub = static_cast<double>(startSub);
if (zone[edge] && zone[edge + 1]) {
int64_t lo = startSub, hi = endSub;
while (hi - lo > 4) {
const int64_t mid = lo + (hi - lo) / 2;
int64_t p[3] = {pLeft[0], pLeft[1], pLeft[2]}; p[axis] = mid;
const bool midRock = blended_field(c, p) >= 0.0;
if (midRock == beforeRock) lo = mid; else hi = mid;
}
rootSub = (lo + hi) * 0.5;
} else {
const double denominator = leftValue - rightValue;
const double t = std::clamp(std::abs(denominator) > 1.0e-12
? leftValue / denominator : 0.5, 0.0, 1.0);
rootSub = startSub + t * static_cast<double>(stepSub);
}
int64_t p32 = std::llround(rootSub * 2.0);
p32=std::clamp<int64_t>(p32,static_cast<int64_t>(start32)+1,static_cast<int64_t>(end32)-1);
if (p32 < std::numeric_limits<int32_t>::min() || p32 > std::numeric_limits<int32_t>::max()) continue;
rebuilt.push_back({static_cast<int32_t>(p32), kBlendCrossingShape,
static_cast<uint8_t>(beforeRock), static_cast<uint8_t>(!beforeRock)});
++out.blendedCrossings;
}
std::sort(rebuilt.begin() + line.offset, rebuilt.end(), [](const Crossing& a, const Crossing& b) {
return a.positionSub32 < b.positionSub32
|| (a.positionSub32 == b.positionSub32 && a.shapeId < b.shapeId);
});
line.count = static_cast<uint16_t>(std::min<size_t>(rebuilt.size() - line.offset, 65535));
counts[li] = line.count;
}
data.crossings.swap(rebuilt);
std::sort(counts.begin(), counts.end());
stats[axis].crossingTotal = std::accumulate(counts.begin(), counts.end(), uint64_t{0});
stats[axis].crossingP50 = counts[counts.size() / 2];
stats[axis].crossingP95 = counts[static_cast<size_t>(0.95 * (counts.size() - 1))];
stats[axis].crossingMax = counts.back();
}
}
uint64_t index_hash_bytes(uint64_t h, const void* data, size_t bytes) {
const auto* p = static_cast<const uint8_t*>(data);
for (size_t i = 0; i < bytes; ++i) { h ^= p[i]; h *= 1099511628211ull; }
return h;
}
uint32_t owner_page_for_edge(int axis, int x, int y, int z) {
int n[3] = {x,y,z};
int cell[3]{};
for (int a = 0; a < 3; ++a) {
int ci;
if (a == axis) ci = n[a] - 1;
else {
const int coreNode = n[a] - kHalo;
if (coreNode > 0 && coreNode < kCoreCells && coreNode % kPageCells == 0) ci = coreNode - 1;
else ci = coreNode;
}
ci = std::clamp(ci, 0, kCoreCells - 1);
cell[a] = ci / kPageCells;
}
return static_cast<uint32_t>((cell[2] * kPagesPerAxis + cell[1]) * kPagesPerAxis + cell[0]);
}
uint64_t edge_key(int axis, int x, int y, int z) {
return (((static_cast<uint64_t>(axis) * kNodes + static_cast<uint64_t>(x)) * kNodes
+ static_cast<uint64_t>(y)) * kNodes + static_cast<uint64_t>(z));
}
struct EdgeDesc { int axis, x, y, z, c0, c1; };
constexpr std::array<EdgeDesc, 12> kEdges = {{
{0,0,0,0,0,1},{1,1,0,0,1,2},{0,0,1,0,2,3},{1,0,0,0,3,0},
{0,0,0,1,4,5},{1,1,0,1,5,6},{0,0,1,1,6,7},{1,0,0,1,7,4},
{2,0,0,0,0,4},{2,1,0,0,1,5},{2,1,1,0,2,6},{2,0,1,0,3,7}
}};
constexpr int kCornerOffset[8][3] = {{0,0,0},{1,0,0},{1,1,0},{0,1,0},{0,0,1},{1,0,1},{1,1,1},{0,1,1}};
constexpr int kFaceCorners[6][4] = {{0,1,2,3},{4,5,6,7},{0,1,5,4},{3,2,6,7},{0,3,7,4},{1,2,6,5}};
constexpr int kFaceEdges[6][4] = {{0,1,2,3},{4,5,6,7},{0,9,4,8},{2,10,6,11},{3,11,7,8},{1,10,5,9}};
struct CrossingRef { Crossing c; uint32_t line; };
bool find_edge_crossing(const AxisData& axisData, int axis, int x, int y, int z,
bool leftRock, bool rightRock, Crossing& out, uint64_t& multi) {
const uint32_t line = line_id(axis, x, y, z);
const LineRange& lr = axisData.lines[line];
const int node = (axis == 0 ? x : axis == 1 ? y : z);
const int localCm = kGridMinCm + node * kCellStepCm;
const int64_t start32 = static_cast<int64_t>(localCm) * kSurfaceSubPerCm;
const int64_t end32 = start32 + kCellStepCm * kSurfaceSubPerCm;
uint32_t found = 0;
for (uint32_t i = 0; i < lr.count; ++i) {
const Crossing& c = axisData.crossings[lr.offset + i];
if (c.positionSub32 <= start32 || c.positionSub32 >= end32) continue;
if ((c.beforeRock != 0) == leftRock && (c.afterRock != 0) == rightRock) {
out = c; ++found;
}
}
if (found > 1) ++multi;
return found != 0;
}
void normal_for_crossing(const Corpus& corpus, const Crossing& c, int axis, int local[3],
double posCm, double outN[3]) {
if (c.shapeId == kBlendCrossingShape) {
int64_t p[3] = {static_cast<int64_t>(local[0]) * kSubPerCm,
static_cast<int64_t>(local[1]) * kSubPerCm,
static_cast<int64_t>(local[2]) * kSubPerCm};
p[axis] = std::llround(posCm * kSubPerCm);
double field{};
blended_field_gradient(corpus,p,field,outN);
const bool wantPositiveAxis = c.beforeRock && !c.afterRock;
const double sign = ((outN[axis] >= 0.0) == wantPositiveAxis) ? 1.0 : -1.0;
double len2 = 0.0;
for (double& v : std::span<double,3>(outN,3)) { v *= sign; len2 += v * v; }
if (len2 <= 1.0e-28) { outN[0] = (axis == 0 ? 1.0 : 0.0); outN[1] = (axis == 1 ? 1.0 : 0.0); outN[2] = (axis == 2 ? 1.0 : 0.0); return; }
const double inv = 1.0 / std::sqrt(len2);
for (double& v : std::span<double,3>(outN,3)) v *= inv;
return;
}
const Shape& s = corpus.shapes[c.shapeId];
double d[3] = {static_cast<double>(local[0] - s.c[0]),
static_cast<double>(local[1] - s.c[1]),
static_cast<double>(local[2] - s.c[2])};
d[axis] = posCm - s.c[axis];
double g[3]{};
for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) g[i] += 2.0 * static_cast<double>(s.q[i][j]) * d[j];
const bool wantPositiveAxis = c.beforeRock && !c.afterRock;
const double sign = ((g[axis] >= 0.0) == wantPositiveAxis) ? 1.0 : -1.0;
double len2 = 0.0;
for (int i = 0; i < 3; ++i) { outN[i] = g[i] * sign; len2 += outN[i] * outN[i]; }
if (len2 <= 1.0e-28) { outN[0] = (axis == 0 ? 1.0 : 0.0); outN[1] = (axis == 1 ? 1.0 : 0.0); outN[2] = (axis == 2 ? 1.0 : 0.0); return; }
const double inv = 1.0 / std::sqrt(len2);
for (double& v : std::span<double,3>(outN,3)) v *= inv;
}
int axis_for_edge(const EdgeDesc& e, int x, int y, int z) { return e.axis; }
void write_vertex_hash(uint64_t& hash, const Vertex& v) { hash = index_hash_bytes(hash, &v, sizeof(v)); }
MeshOutput emit_near_mesh(const Corpus& c, const AxisData axes[3], const AxisData& zSigns,
const std::vector<uint64_t>& active, int workers) {
(void)workers;
MeshOutput out;
std::unordered_map<uint64_t, uint32_t> edgeToVertex;
edgeToVertex.reserve(700000);
out.pages.reserve(kPagesPerAxis * kPagesPerAxis * kPagesPerAxis);
std::array<uint64_t, 8> cornerSigns{};
std::array<int, 3> local{};
uint64_t multiEdges = 0;
auto add_vertex = [&](const EdgeDesc& e, int cellX, int cellY, int cellZ,
const Crossing& crossing) -> uint32_t {
const int ex = cellX + e.x, ey = cellY + e.y, ez = cellZ + e.z;
const uint64_t key = edge_key(e.axis, ex, ey, ez);
auto it = edgeToVertex.find(key);
if (it != edgeToVertex.end()) return it->second;
int p[3] = {kGridMinCm + ex * kCellStepCm,
kGridMinCm + ey * kCellStepCm,
kGridMinCm + ez * kCellStepCm};
const double localPosCm = crossing.positionSub32 / static_cast<double>(kSurfaceSubPerCm);
p[e.axis] = static_cast<int>(std::floor(localPosCm));
double n[3]; normal_for_crossing(c, crossing, e.axis, p, localPosCm, n);
const double world[3] = {
kWorldOriginCm[0] + (e.axis == 0 ? localPosCm : (kGridMinCm + ex * kCellStepCm)),
kWorldOriginCm[1] + (e.axis == 1 ? localPosCm : (kGridMinCm + ey * kCellStepCm)),
kWorldOriginCm[2] + (e.axis == 2 ? localPosCm : (kGridMinCm + ez * kCellStepCm))};
Vertex v{static_cast<float>(world[0] * 0.01), static_cast<float>(world[1] * 0.01),
static_cast<float>(world[2] * 0.01), static_cast<float>(n[0]),
static_cast<float>(n[1]), static_cast<float>(n[2])};
const uint32_t owner = owner_page_for_edge(e.axis, ex, ey, ez);
const uint32_t index = static_cast<uint32_t>(out.vertices.size());
out.vertices.push_back({key, owner, v});
edgeToVertex.emplace(key, index);
const int edgeNodes[3] = {ex,ey,ez};
for (int a = 0; a < 3; ++a) {
const int coreNode = edgeNodes[a] - kHalo;
if (coreNode > 0 && coreNode < kCoreCells && coreNode % kPageCells == 0) {
++out.pageBorderVertices;
break;
}
}
return index;
};
auto emit_triangle = [&](uint32_t a, uint32_t b, uint32_t d) {
const Vertex& va = out.vertices[a].v; const Vertex& vb = out.vertices[b].v; const Vertex& vd = out.vertices[d].v;
const double ux = vb.x - va.x, uy = vb.y - va.y, uz = vb.z - va.z;
const double vx = vd.x - va.x, vy = vd.y - va.y, vz = vd.z - va.z;
const double cx = uy * vz - uz * vy, cy = uz * vx - ux * vz, cz = ux * vy - uy * vx;
const double nx = va.nx + vb.nx + vd.nx, ny = va.ny + vb.ny + vd.ny, nz = va.nz + vb.nz + vd.nz;
if (cx * nx + cy * ny + cz * nz < 0.0) std::swap(b, d);
out.indices.push_back(a); out.indices.push_back(b); out.indices.push_back(d);
};
auto shape_hits_lattice = [&](const Shape& s) {
int lo[3], hi[3];
for (int a = 0; a < 3; ++a) {
lo[a] = std::clamp(ceil_div(s.lo[a] - kGridMinCm, kCellStepCm), 0, kNodes - 1);
hi[a] = std::clamp(floor_div(s.hi[a] - kGridMinCm, kCellStepCm), 0, kNodes - 1);
}
for (int z = lo[2]; z <= hi[2]; ++z) for (int y = lo[1]; y <= hi[1]; ++y)
for (int x = lo[0]; x <= hi[0]; ++x) {
const int64_t p[3] = {
static_cast<int64_t>(kGridMinCm + x * kCellStepCm) * kSubPerCm,
static_cast<int64_t>(kGridMinCm + y * kCellStepCm) * kSubPerCm,
static_cast<int64_t>(kGridMinCm + z * kCellStepCm) * kSubPerCm};
if (contains_sub(s, p)) return true;
}
return false;
};
for (int pz = 0; pz < kPagesPerAxis; ++pz) for (int py = 0; py < kPagesPerAxis; ++py)
for (int px = 0; px < kPagesPerAxis; ++px) {
const uint32_t page = static_cast<uint32_t>((pz * kPagesPerAxis + py) * kPagesPerAxis + px);
const uint32_t start = static_cast<uint32_t>(out.indices.size());
const size_t vstart = out.vertices.size();
for (int iy = py * kPageCells; iy < (py + 1) * kPageCells; ++iy)
for (int ix = px * kPageCells; ix < (px + 1) * kPageCells; ++ix) {
const int nx = ix + kHalo, ny = iy + kHalo;
for (int zb = pz; zb <= pz; ++zb) {
uint64_t mask = active[(static_cast<size_t>(ix) * kCoreCells + iy) * 4 + zb];
while (mask) {
const unsigned bz = std::countr_zero(mask);
mask &= mask - 1;
const int iz = zb * 64 + static_cast<int>(bz);
const int cellX = nx, cellY = ny, cellZ = iz + kHalo;
bool rock[8]{};
for (int k = 0; k < 8; ++k) {
const int x = cellX + kCornerOffset[k][0];
const int y = cellY + kCornerOffset[k][1];
const int z = cellZ + kCornerOffset[k][2];
rock[k] = get_sign(zSigns, line_id(2, x, y, z), z);
}
bool edgeCross[12]{};
uint32_t edgeVertex[12]{};
for (int ei = 0; ei < 12; ++ei) {
const EdgeDesc& e = kEdges[ei];
if (rock[e.c0] == rock[e.c1]) continue;
const int sx = cellX + e.x, sy = cellY + e.y, sz = cellZ + e.z;
const bool c0IsLow = kCornerOffset[e.c0][e.axis] < kCornerOffset[e.c1][e.axis];
const bool lowRock = c0IsLow ? rock[e.c0] : rock[e.c1];
const bool highRock = c0IsLow ? rock[e.c1] : rock[e.c0];
Crossing crossing;
if (!find_edge_crossing(axes[e.axis], e.axis, sx, sy, sz,
lowRock, highRock, crossing, multiEdges)) {
++out.missingAnalyticCrossings;
continue;
}
edgeCross[ei] = true;
edgeVertex[ei] = add_vertex(e, cellX, cellY, cellZ, crossing);
}
std::array<std::array<int,2>,12> adj{};
std::array<uint8_t,12> degree{};
auto connect = [&](int a, int b) {
if (degree[a] < 2 && degree[b] < 2) {
adj[a][degree[a]++] = b; adj[b][degree[b]++] = a;
}
};
for (int f = 0; f < 6; ++f) {
int crossed[4], nCross = 0;
for (int k = 0; k < 4; ++k) if (edgeCross[kFaceEdges[f][k]]) crossed[nCross++] = k;
if (nCross == 2) connect(kFaceEdges[f][crossed[0]], kFaceEdges[f][crossed[1]]);
else if (nCross == 4) {
++out.ambiguousFaces;
int pSub[3] = {kGridMinCm + cellX * kCellStepCm,
kGridMinCm + cellY * kCellStepCm,
kGridMinCm + cellZ * kCellStepCm};
int sumOffset[3]{};
for (int k = 0; k < 4; ++k) for (int a = 0; a < 3; ++a)
sumOffset[a] += kCornerOffset[kFaceCorners[f][k]][a];
int64_t q[3];
for (int a = 0; a < 3; ++a) q[a] = (static_cast<int64_t>(pSub[a]) * 4 + sumOffset[a]) * 4;
const bool centerRock = point_rock(c, q);
const bool s0 = rock[kFaceCorners[f][0]];
if (centerRock == s0) {
connect(kFaceEdges[f][0], kFaceEdges[f][1]);
connect(kFaceEdges[f][2], kFaceEdges[f][3]);
} else {
connect(kFaceEdges[f][3], kFaceEdges[f][0]);
connect(kFaceEdges[f][1], kFaceEdges[f][2]);
}
}
}
for (int ei = 0; ei < 12; ++ei)
if (edgeCross[ei] && degree[ei] != 2) ++out.contourDegreeFaults;
std::array<uint8_t,12> visited{};
for (int startEdge = 0; startEdge < 12; ++startEdge) {
if (!edgeCross[startEdge] || visited[startEdge] || degree[startEdge] != 2) continue;
std::vector<int> loop;
int prev = -1, cur = startEdge;
for (int guard = 0; guard < 16; ++guard) {
if (visited[cur]) break;
visited[cur] = 1; loop.push_back(cur);
const int next = adj[cur][0] == prev ? adj[cur][1] : adj[cur][0];
prev = cur; cur = next;
if (cur == startEdge) break;
}
if (cur != startEdge) ++out.openContourLoops;
if (loop.size() >= 3) for (size_t i = 1; i + 1 < loop.size(); ++i)
emit_triangle(edgeVertex[loop[0]], edgeVertex[loop[i]], edgeVertex[loop[i + 1]]);
}
}
}
}
out.pages.push_back({page, start, static_cast<uint32_t>(out.indices.size() - start),
static_cast<uint32_t>(vstart), static_cast<uint32_t>(out.vertices.size() - vstart)});
}
// Explicit surface fallback for sub-cell strata missed by every structural lattice point.
constexpr int rings = 8, sectors = 24;
for (const Shape& s : c.shapes) {
if (s.kind != FeatureKind::Stratum || s.radius[2] >= kCellStepCm / 2) continue;
++out.thinFeaturesDetected;
if (shape_hits_lattice(s)) continue;
++out.thinFeaturesHiddenInLattice;
const uint32_t page = owner_page_for_edge(0, std::clamp(kHalo + (s.c[0] - kGridMinCm) / kCellStepCm, 1, kCoreCells),
std::clamp(kHalo + (s.c[1] - kGridMinCm) / kCellStepCm, 1, kCoreCells),
std::clamp(kHalo + (s.c[2] - kGridMinCm) / kCellStepCm, 1, kCoreCells));
const uint32_t start = static_cast<uint32_t>(out.indices.size());
const uint32_t base = static_cast<uint32_t>(out.vertices.size());
std::vector<std::array<double, 3>> localPoints;
std::vector<std::array<double, 3>> surfaceNormals;
localPoints.reserve((rings + 1) * sectors);
surfaceNormals.reserve((rings + 1) * sectors);
for (int r = 0; r <= rings; ++r) {
const double phi = -1.5707963267948966 + 3.1415926535897932 * r / rings;
for (int a = 0; a < sectors; ++a) {
const double theta = 6.2831853071795864 * a / sectors;
const double u[3] = {std::cos(phi) * std::cos(theta), std::cos(phi) * std::sin(theta), std::sin(phi)};
double localP[3]{};
for (int axis = 0; axis < 3; ++axis) {
for (int k = 0; k < 3; ++k)
localP[axis] += static_cast<double>(s.rot[axis][k]) / 32768.0 * s.radius[k] * u[k];
}
double n[3]{};
for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j)
n[i] += 2.0 * s.q[i][j] * localP[j];
double len = std::sqrt(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]);
if (len > 0) for (double& v : n) v /= len;
if (s.op == Operation::CarveAir) for (double& v : n) v = -v;
localPoints.push_back({localP[0], localP[1], localP[2]});
surfaceNormals.push_back({n[0],n[1],n[2]});
const double wx = (kWorldOriginCm[0] + s.c[0] + localP[0]) * 0.01;
const double wy = (kWorldOriginCm[1] + s.c[1] + localP[1]) * 0.01;
const double wz = (kWorldOriginCm[2] + s.c[2] + localP[2]) * 0.01;
const uint64_t thinKey = (uint64_t{1} << 63) | (static_cast<uint64_t>(s.id) << 32)
| (static_cast<uint64_t>(r) << 16) | static_cast<uint64_t>(a);
out.vertices.push_back({thinKey, page,
{static_cast<float>(wx),static_cast<float>(wy),static_cast<float>(wz),
static_cast<float>(n[0]),static_cast<float>(n[1]),static_cast<float>(n[2])}});
}
}
for (int r = 0; r < rings; ++r) for (int a = 0; a < sectors; ++a) {
const int an = (a + 1) % sectors;
const uint32_t v00 = base + r * sectors + a, v01 = base + r * sectors + an;
const uint32_t v10 = base + (r + 1) * sectors + a, v11 = base + (r + 1) * sectors + an;
const uint32_t tris[2][3] = {{v00,v10,v01},{v01,v10,v11}};
for (int ti = 0; ti < 2; ++ti) {
if ((r == 0 && ti == 0) || (r == rings - 1 && ti == 1)) continue;
double p[3]{}, n[3]{};
for (int v : tris[ti]) for (int a3 = 0; a3 < 3; ++a3) {
p[a3] += (s.c[a3] + localPoints[v - base][a3]) / 3.0;
n[a3] += surfaceNormals[v - base][a3] / 3.0;
}
const double nlen = std::sqrt(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]);
if (nlen > 0) for (double& v : n) v /= nlen;
int64_t plus[3], minus[3];
for (int a3 = 0; a3 < 3; ++a3) {
plus[a3] = std::llround((p[a3] + n[a3]) * kSubPerCm);
minus[a3] = std::llround((p[a3] - n[a3]) * kSubPerCm);
}
if (point_rock(c, plus) == point_rock(c, minus)) continue;
emit_triangle(tris[ti][0], tris[ti][1], tris[ti][2]);
++out.explicitThinTriangles;
}
}
if (out.indices.size() != start) {
++out.thinFeaturesMeshed;
out.pages.push_back({page, start, static_cast<uint32_t>(out.indices.size() - start), base,
static_cast<uint32_t>(out.vertices.size() - base)});
} else {
out.vertices.resize(base);
}
}
// Stable ownership and vertex order by (owner page, edge key); index order remains page-owned.
std::vector<uint32_t> permutation(out.vertices.size());
std::iota(permutation.begin(), permutation.end(), 0u);
std::sort(permutation.begin(), permutation.end(), [&](uint32_t a, uint32_t b) {
if (out.vertices[a].ownerPage != out.vertices[b].ownerPage)
return out.vertices[a].ownerPage < out.vertices[b].ownerPage;
return out.vertices[a].key < out.vertices[b].key;
});
std::vector<uint32_t> remap(out.vertices.size());
std::vector<TempVertex> sortedVertices;
sortedVertices.reserve(out.vertices.size());
for (uint32_t i = 0; i < permutation.size(); ++i) {
remap[permutation[i]] = i;
sortedVertices.push_back(out.vertices[permutation[i]]);
}
for (uint32_t& index : out.indices) index = remap[index];
out.vertices.swap(sortedVertices);
for (PageRange& p : out.pages) {
const auto first = std::lower_bound(out.vertices.begin(), out.vertices.end(), p.page,
[](const TempVertex& v, uint32_t owner) { return v.ownerPage < owner; });
const auto last = std::upper_bound(first, out.vertices.end(), p.page,
[](uint32_t owner, const TempVertex& v) { return owner < v.ownerPage; });
p.vertexStart = static_cast<uint32_t>(first - out.vertices.begin());
p.vertexCount = static_cast<uint32_t>(last - first);
}
out.triangles = out.indices.size() / 3;
out.multiCrossEdges = multiEdges;
uint64_t h = 1469598103934665603ull;
for (const TempVertex& v : out.vertices) write_vertex_hash(h, v.v);
h = index_hash_bytes(h, out.indices.data(), out.indices.size() * sizeof(uint32_t));
for (const PageRange& p : out.pages) h = index_hash_bytes(h, &p, sizeof(p));
out.hash = h;
out.bytes = out.vertices.size() * sizeof(Vertex) + out.indices.size() * sizeof(uint32_t)
+ out.pages.size() * sizeof(PageRange);
return out;
}
uint32_t cell_linear_key(int x, int y, int z) {
return static_cast<uint32_t>((static_cast<size_t>(x) * kCoreCells + y) * kCoreCells + z);
}
uint32_t owner_page_for_cell(int x, int y, int z) {
return static_cast<uint32_t>(((z / kPageCells) * kPagesPerAxis + y / kPageCells) * kPagesPerAxis + x / kPageCells);
}
bool solve_qef3(double a[3][3], double b[3], double out[3]) {
double m[3][4]{};
for (int r = 0; r < 3; ++r) { for (int c = 0; c < 3; ++c) m[r][c] = a[r][c]; m[r][3] = b[r]; }
for (int c = 0; c < 3; ++c) {
int pivot = c;
for (int r = c + 1; r < 3; ++r) if (std::abs(m[r][c]) > std::abs(m[pivot][c])) pivot = r;
if (std::abs(m[pivot][c]) < 1.0e-12) return false;
if (pivot != c) for (int j = c; j < 4; ++j) std::swap(m[pivot][j], m[c][j]);
const double inv = 1.0 / m[c][c];
for (int j = c; j < 4; ++j) m[c][j] *= inv;
for (int r = 0; r < 3; ++r) if (r != c) {
const double f = m[r][c];
for (int j = c; j < 4; ++j) m[r][j] -= f * m[c][j];
}
}
for (int i = 0; i < 3; ++i) out[i] = m[i][3];
return true;
}
MeshOutput emit_dual_contour_mesh(const Corpus& c, const AxisData axes[3], const AxisData& zSigns,
const std::vector<uint64_t>& active) {
MeshOutput out;
const size_t estimated = 300000;
out.vertices.reserve(estimated);
struct CellTopology { std::array<uint32_t,12> edgeVertex; CellTopology() { edgeVertex.fill(std::numeric_limits<uint32_t>::max()); } };
std::unordered_map<uint32_t,CellTopology> cellTopology;
cellTopology.reserve(estimated);
std::array<std::vector<uint32_t>, kPagesPerAxis * kPagesPerAxis * kPagesPerAxis> pageIndices;
uint64_t multiEdges = 0;
struct Hermite { Crossing crossing{}; double normal[3]{}; };
std::unordered_map<uint64_t,Hermite> hermites;
hermites.reserve(axes[0].crossings.size()+axes[1].crossings.size()+axes[2].crossings.size());
const int64_t domainStart32 = static_cast<int64_t>(kGridMinCm) * kSurfaceSubPerCm;
const int64_t edgeSpan32 = static_cast<int64_t>(kCellStepCm) * kSurfaceSubPerCm;
for (int axis = 0; axis < 3; ++axis) for (uint32_t li = 0; li < axes[axis].lines.size(); ++li) {
int p0, p1; decode_line(axis, li, p0, p1);
const int a1 = (axis + 1) % 3, a2 = (axis + 2) % 3;
for (uint32_t i = 0; i < axes[axis].lines[li].count; ++i) {
const Crossing& crossing = axes[axis].crossings[axes[axis].lines[li].offset+i];
const int edge = static_cast<int>(floor_div64(static_cast<int64_t>(crossing.positionSub32)-domainStart32, edgeSpan32));
if (edge < 0 || edge >= kNodes-1) continue;
int coord[3]{}; coord[axis]=edge; coord[a1]=p0; coord[a2]=p1;
int localCm[3] = {kGridMinCm+coord[0]*kCellStepCm,kGridMinCm+coord[1]*kCellStepCm,kGridMinCm+coord[2]*kCellStepCm};
const double posCm = crossing.positionSub32/static_cast<double>(kSurfaceSubPerCm);
localCm[axis] = static_cast<int>(std::floor(posCm));
Hermite h{}; h.crossing=crossing;
normal_for_crossing(c,crossing,axis,localCm,posCm,h.normal);
const uint64_t key = edge_key(axis,coord[0],coord[1],coord[2])
| (crossing.beforeRock ? (uint64_t{1}<<62) : 0);
auto [it,inserted] = hermites.emplace(key,h);
if (!inserted) { ++multiEdges; it->second=h; }
}
}
auto addQefComponent = [&](int ix,int iy,int iz,int cellX,int cellY,int cellZ,int component,
const std::vector<int>& loop,const Hermite edgeHermite[12],CellTopology& topology) {
double ata[3][3]{},atb[3]{},avg[3]{},normalSum[3]{};
for (int ei : loop) {
const Hermite& h=edgeHermite[ei];
const EdgeDesc& e=kEdges[ei];
const int ex=cellX+e.x,ey=cellY+e.y,ez=cellZ+e.z;
const double crossingCm=h.crossing.positionSub32/static_cast<double>(kSurfaceSubPerCm);
const double* n=h.normal;
double hp[3]={static_cast<double>(kGridMinCm+ex*kCellStepCm-(kGridMinCm+cellX*kCellStepCm)),
static_cast<double>(kGridMinCm+ey*kCellStepCm-(kGridMinCm+cellY*kCellStepCm)),
static_cast<double>(kGridMinCm+ez*kCellStepCm-(kGridMinCm+cellZ*kCellStepCm))};
hp[e.axis]=crossingCm-(kGridMinCm+(e.axis==0?cellX:e.axis==1?cellY:cellZ)*kCellStepCm);
const double ndotp=n[0]*hp[0]+n[1]*hp[1]+n[2]*hp[2];
for(int r=0;r<3;++r){atb[r]+=n[r]*ndotp;avg[r]+=hp[r];normalSum[r]+=n[r];for(int col=0;col<3;++col)ata[r][col]+=n[r]*n[col];}
}
if(loop.empty())return;
const int count=static_cast<int>(loop.size());
for(double& v:avg)v/=count;
const double regularizer=1.0e-5*count;
for(int a=0;a<3;++a){ata[a][a]+=regularizer;atb[a]+=regularizer*avg[a];}
double qef[3]{};
if(!solve_qef3(ata,atb,qef))for(int a=0;a<3;++a)qef[a]=avg[a];
for(int a=0;a<3;++a){if(qef[a]<0.0||qef[a]>kCellStepCm)++out.qefClampedVertices;qef[a]=std::clamp(qef[a],0.0,static_cast<double>(kCellStepCm));}
double nlen=std::sqrt(normalSum[0]*normalSum[0]+normalSum[1]*normalSum[1]+normalSum[2]*normalSum[2]);
if(nlen<1.0e-20){normalSum[0]=0;normalSum[1]=0;normalSum[2]=1;nlen=1;}
const double wx=(kWorldOriginCm[0]+kGridMinCm+cellX*kCellStepCm+qef[0])*0.01;
const double wy=(kWorldOriginCm[1]+kGridMinCm+cellY*kCellStepCm+qef[1])*0.01;
const double wz=(kWorldOriginCm[2]+kGridMinCm+cellZ*kCellStepCm+qef[2])*0.01;
const uint32_t page=owner_page_for_cell(ix,iy,iz);
const uint64_t cellId=cell_linear_key(ix,iy,iz);
const uint64_t key=(uint64_t{4}<<60)|((cellId<<4)|static_cast<uint64_t>(component&15));
const uint32_t vertex=static_cast<uint32_t>(out.vertices.size());
out.vertices.push_back({key,page,{static_cast<float>(wx),static_cast<float>(wy),static_cast<float>(wz),
static_cast<float>(normalSum[0]/nlen),static_cast<float>(normalSum[1]/nlen),static_cast<float>(normalSum[2]/nlen)}});
for(int ei:loop)topology.edgeVertex[ei]=vertex;
if((ix>0&&ix%kPageCells==0)||(ix+1<kCoreCells&&(ix+1)%kPageCells==0)
||(iy>0&&iy%kPageCells==0)||(iy+1<kCoreCells&&(iy+1)%kPageCells==0)
||(iz>0&&iz%kPageCells==0)||(iz+1<kCoreCells&&(iz+1)%kPageCells==0))++out.pageBorderVertices;
};
for (int ix = 0; ix < kCoreCells; ++ix) for (int iy = 0; iy < kCoreCells; ++iy) {
for (int block = 0; block < 4; ++block) {
uint64_t mask = active[(static_cast<size_t>(ix) * kCoreCells + iy) * 4 + block];
while (mask) {
const unsigned bit=std::countr_zero(mask);mask&=mask-1;
const int iz=block*64+static_cast<int>(bit),cellX=ix+kHalo,cellY=iy+kHalo,cellZ=iz+kHalo;
bool rock[8]{};
for(int k=0;k<8;++k){const int x=cellX+kCornerOffset[k][0],y=cellY+kCornerOffset[k][1],z=cellZ+kCornerOffset[k][2];rock[k]=get_sign(zSigns,line_id(2,x,y,z),z)!=0;}
bool edgeCross[12]{};
Hermite edgeHermite[12]{};
for(int ei=0;ei<12;++ei){
const EdgeDesc& e=kEdges[ei];if(rock[e.c0]==rock[e.c1])continue;
const int ex=cellX+e.x,ey=cellY+e.y,ez=cellZ+e.z;
const bool c0low=kCornerOffset[e.c0][e.axis]<kCornerOffset[e.c1][e.axis];
const bool lowRock=c0low?rock[e.c0]:rock[e.c1],highRock=c0low?rock[e.c1]:rock[e.c0];
const uint64_t key=edge_key(e.axis,ex,ey,ez)|(lowRock?(uint64_t{1}<<62):0);
auto hit=hermites.find(key);
if(hit!=hermites.end()){edgeHermite[ei]=hit->second;edgeCross[ei]=true;continue;}
Crossing crossing{};
if(!find_edge_crossing(axes[e.axis],e.axis,ex,ey,ez,lowRock,highRock,crossing,multiEdges)){
++out.missingAnalyticCrossings;
continue;
}
const double pos=crossing.positionSub32/static_cast<double>(kSurfaceSubPerCm);
int localCm[3]={kGridMinCm+ex*kCellStepCm,kGridMinCm+ey*kCellStepCm,kGridMinCm+ez*kCellStepCm};localCm[e.axis]=static_cast<int>(std::floor(pos));
edgeHermite[ei].crossing=crossing;normal_for_crossing(c,crossing,e.axis,localCm,pos,edgeHermite[ei].normal);
edgeCross[ei]=true;++out.cachedHermiteFallbacks;
}
std::array<std::array<int,2>,12> adj{};std::array<uint8_t,12> degree{};
auto connect=[&](int a,int b){if(degree[a]<2&&degree[b]<2){adj[a][degree[a]++]=b;adj[b][degree[b]++]=a;}};
for(int f=0;f<6;++f){
int crossed[4],nCross=0;for(int k=0;k<4;++k)if(edgeCross[kFaceEdges[f][k]])crossed[nCross++]=k;
if(nCross==2)connect(kFaceEdges[f][crossed[0]],kFaceEdges[f][crossed[1]]);
else if(nCross==4){
++out.ambiguousFaces;
int64_t q[3]={static_cast<int64_t>(kGridMinCm+cellX*kCellStepCm)*kSubPerCm,
static_cast<int64_t>(kGridMinCm+cellY*kCellStepCm)*kSubPerCm,
static_cast<int64_t>(kGridMinCm+cellZ*kCellStepCm)*kSubPerCm};
int sums[3]{};for(int k=0;k<4;++k)for(int a=0;a<3;++a)sums[a]+=kCornerOffset[kFaceCorners[f][k]][a];
for(int a=0;a<3;++a)q[a]+=sums[a]*(kCellStepCm*kSubPerCm/4);
const bool centerRock=point_rock_reference(c,q),s0=rock[kFaceCorners[f][0]];
if(centerRock==s0){connect(kFaceEdges[f][0],kFaceEdges[f][1]);connect(kFaceEdges[f][2],kFaceEdges[f][3]);}
else{connect(kFaceEdges[f][3],kFaceEdges[f][0]);connect(kFaceEdges[f][1],kFaceEdges[f][2]);}
}
}
CellTopology topology;
std::array<uint8_t,12> visited{};int component=0;
for(int start=0;start<12;++start){
if(!edgeCross[start]||visited[start])continue;
if(degree[start]!=2){++out.contourDegreeFaults;visited[start]=1;continue;}
std::vector<int> loop;int prev=-1,cur=start;
for(int guard=0;guard<16;++guard){if(visited[cur])break;visited[cur]=1;loop.push_back(cur);const int next=adj[cur][0]==prev?adj[cur][1]:adj[cur][0];prev=cur;cur=next;if(cur==start)break;}
if(cur!=start){++out.openContourLoops;continue;}
if(loop.size()>=3)addQefComponent(ix,iy,iz,cellX,cellY,cellZ,component++,loop,edgeHermite,topology);
}
if(component>0)cellTopology.emplace(cell_linear_key(ix,iy,iz),std::move(topology));
}
}
}
auto getCell = [&](int x,int y,int z,int localEdge)->uint32_t {
auto it=cellTopology.find(cell_linear_key(x,y,z));
return it==cellTopology.end()?std::numeric_limits<uint32_t>::max():it->second.edgeVertex[localEdge];
};
auto emitTriangle = [&](uint32_t page, uint32_t a, uint32_t b, uint32_t d, int axis, double sign) {
const Vertex& va = out.vertices[a].v; const Vertex& vb = out.vertices[b].v; const Vertex& vd = out.vertices[d].v;
const double ux = vb.x - va.x, uy = vb.y - va.y, uz = vb.z - va.z;
const double vx = vd.x - va.x, vy = vd.y - va.y, vz = vd.z - va.z;
const double n[3] = {uy*vz-uz*vy, uz*vx-ux*vz, ux*vy-uy*vx};
if (std::abs(n[0]) + std::abs(n[1]) + std::abs(n[2]) < 1.0e-12) ++out.degenerateTriangles;
if (n[axis] * sign < 0.0) std::swap(b, d);
auto& indices = pageIndices[page];
indices.push_back(a); indices.push_back(b); indices.push_back(d);
};
auto emitQuad = [&](int axis, int xNode, int yNode, int zNode, std::array<std::array<int,3>,4> cells,
std::array<int,4> localEdges,bool lowRock) {
uint32_t v[4];
for (int i = 0; i < 4; ++i) v[i] = getCell(cells[i][0], cells[i][1], cells[i][2],localEdges[i]);
if (std::any_of(std::begin(v), std::end(v), [](uint32_t q) { return q == std::numeric_limits<uint32_t>::max(); })) {
++out.contourDegreeFaults; return;
}
const uint32_t page = owner_page_for_edge(axis, xNode, yNode, zNode);
const double sign = lowRock ? 1.0 : -1.0;
emitTriangle(page, v[0], v[1], v[2], axis, sign);
emitTriangle(page, v[0], v[2], v[3], axis, sign);
};
std::unordered_set<uint64_t> emittedEdges;
emittedEdges.reserve(300000);
for (int axis = 0; axis < 3; ++axis) {
const int a1 = (axis+1)%3, a2 = (axis+2)%3;
for (uint32_t li = 0; li < axes[axis].lines.size(); ++li) {
int p0,p1; decode_line(axis,li,p0,p1);
for (uint32_t ci = 0; ci < axes[axis].lines[li].count; ++ci) {
const Crossing& crossing = axes[axis].crossings[axes[axis].lines[li].offset+ci];
const int edge = static_cast<int>(floor_div64(static_cast<int64_t>(crossing.positionSub32)-domainStart32,edgeSpan32));
if (edge < 0 || edge >= kNodes-1) continue;
const bool lowRock = get_sign(axes[axis],li,edge) != 0;
const bool highRock = get_sign(axes[axis],li,edge+1) != 0;
if (lowRock == highRock || (crossing.beforeRock != 0) != lowRock
|| (crossing.afterRock != 0) != highRock) continue;
int coord[3]{}; coord[axis]=edge; coord[a1]=p0; coord[a2]=p1;
if (coord[axis] < kHalo || coord[axis] >= kHalo+kCoreCells
|| coord[a1] <= kHalo || coord[a1] >= kHalo+kCoreCells
|| coord[a2] <= kHalo || coord[a2] >= kHalo+kCoreCells) continue;
const uint64_t key = edge_key(axis,coord[0],coord[1],coord[2]);
if (!emittedEdges.insert(key).second) continue;
if (axis == 0) {
const int cx=coord[0]-kHalo, yl=coord[1]-kHalo-1, yh=coord[1]-kHalo;
const int zl=coord[2]-kHalo-1, zh=coord[2]-kHalo;
emitQuad(axis,coord[0],coord[1],coord[2],{{{cx,yl,zl},{cx,yh,zl},{cx,yh,zh},{cx,yl,zh}}},{6,4,0,2},lowRock);
} else if (axis == 1) {
const int cy=coord[1]-kHalo, xl=coord[0]-kHalo-1, xh=coord[0]-kHalo;
const int zl=coord[2]-kHalo-1, zh=coord[2]-kHalo;
emitQuad(axis,coord[0],coord[1],coord[2],{{{xl,cy,zl},{xl,cy,zh},{xh,cy,zh},{xh,cy,zl}}},{5,1,3,7},lowRock);
} else {
const int cz=coord[2]-kHalo, xl=coord[0]-kHalo-1, xh=coord[0]-kHalo;
const int yl=coord[1]-kHalo-1, yh=coord[1]-kHalo;
emitQuad(axis,coord[0],coord[1],coord[2],{{{xl,yl,cz},{xh,yl,cz},{xh,yh,cz},{xl,yh,cz}}},{10,11,8,9},lowRock);
}
}
}
}
// Explicit Hermite-like patches retain sub-cell strata that have no sign-changing lattice edge.
auto shapeHitsLattice = [&](const Shape& s) {
int lo[3], hi[3];
for (int a = 0; a < 3; ++a) {
lo[a] = std::clamp(ceil_div(s.lo[a] - kGridMinCm, kCellStepCm), 0, kNodes - 1);
hi[a] = std::clamp(floor_div(s.hi[a] - kGridMinCm, kCellStepCm), 0, kNodes - 1);
}
for (int z = lo[2]; z <= hi[2]; ++z) for (int y = lo[1]; y <= hi[1]; ++y) for (int x = lo[0]; x <= hi[0]; ++x) {
const int64_t p[3] = {static_cast<int64_t>(kGridMinCm+x*kCellStepCm)*kSubPerCm,
static_cast<int64_t>(kGridMinCm+y*kCellStepCm)*kSubPerCm,
static_cast<int64_t>(kGridMinCm+z*kCellStepCm)*kSubPerCm};
if (contains_sub(s, p)) return true;
}
return false;
};
constexpr int rings = 8, sectors = 24;
for (const Shape& s : c.shapes) {
if (s.kind != FeatureKind::Stratum || s.radius[2] >= kCellStepCm / 2) continue;
++out.thinFeaturesDetected;
if (shapeHitsLattice(s)) continue;
++out.thinFeaturesHiddenInLattice;
const int cx = std::clamp((s.c[0] - kGridMinCm) / kCellStepCm, 0, kCoreCells - 1);
const int cy = std::clamp((s.c[1] - kGridMinCm) / kCellStepCm, 0, kCoreCells - 1);
const int cz = std::clamp((s.c[2] - kGridMinCm) / kCellStepCm, 0, kCoreCells - 1);
const uint32_t page = owner_page_for_cell(cx, cy, cz);
const uint32_t base = static_cast<uint32_t>(out.vertices.size());
std::vector<std::array<double,3>> localPoints, surfaceNormals;
localPoints.reserve((rings+1)*sectors); surfaceNormals.reserve((rings+1)*sectors);
for (int r = 0; r <= rings; ++r) for (int a = 0; a < sectors; ++a) {
const double phi = -1.5707963267948966 + 3.1415926535897932 * r / rings;
const double theta = 6.2831853071795864 * a / sectors;
const double u[3] = {std::cos(phi)*std::cos(theta), std::cos(phi)*std::sin(theta), std::sin(phi)};
double localP[3]{};
for (int axis = 0; axis < 3; ++axis) for (int k = 0; k < 3; ++k)
localP[axis] += static_cast<double>(s.rot[axis][k])/32768.0 * s.radius[k] * u[k];
double n[3]{};
for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) n[i] += 2.0*s.q[i][j]*localP[j];
double len = std::sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);
if (len > 0) for (double& v : n) v /= len;
if (s.op == Operation::CarveAir) for (double& v : n) v = -v;
localPoints.push_back({localP[0],localP[1],localP[2]});
surfaceNormals.push_back({n[0],n[1],n[2]});
const uint64_t key = (uint64_t{1} << 63) | (static_cast<uint64_t>(s.id) << 32)
| (static_cast<uint64_t>(r) << 16) | static_cast<uint64_t>(a);
out.vertices.push_back({key, page,
{static_cast<float>((kWorldOriginCm[0]+s.c[0]+localP[0])*0.01),
static_cast<float>((kWorldOriginCm[1]+s.c[1]+localP[1])*0.01),
static_cast<float>((kWorldOriginCm[2]+s.c[2]+localP[2])*0.01),
static_cast<float>(n[0]),static_cast<float>(n[1]),static_cast<float>(n[2])}});
}
const size_t before = pageIndices[page].size();
auto thinTriangle = [&](uint32_t a, uint32_t b, uint32_t d) {
double p[3]{}, n[3]{};
for (uint32_t v : {a,b,d}) for (int ax = 0; ax < 3; ++ax) {
p[ax] += (s.c[ax] + localPoints[v-base][ax])/3.0;
n[ax] += surfaceNormals[v-base][ax]/3.0;
}
const double nl = std::sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);
if (nl <= 0) return;
for (double& v : n) v /= nl;
int64_t plus[3], minus[3];
for (int ax=0;ax<3;++ax) { plus[ax]=std::llround((p[ax]+n[ax])*kSubPerCm);minus[ax]=std::llround((p[ax]-n[ax])*kSubPerCm); }
if (!s.auditThin && point_rock_reference(c,plus)==point_rock_reference(c,minus)) return;
// The deliberately isolated audit seam stays closed; per-triangle CSG clipping
// otherwise leaves open boundaries on the thin fallback.
emitTriangle(page, a, b, d, 2, 1.0);
++out.explicitThinTriangles;
};
for (int r = 0; r < rings; ++r) for (int a = 0; a < sectors; ++a) {
const int an = (a+1)%sectors;
const uint32_t south=base, north=base+rings*sectors;
const uint32_t v00=r==0?south:base+r*sectors+a, v01=r==0?south:base+r*sectors+an;
const uint32_t v10=r+1==rings?north:base+(r+1)*sectors+a;
const uint32_t v11=r+1==rings?north:base+(r+1)*sectors+an;
if (!(r==0)) thinTriangle(v00,v10,v01);
if (!(r==rings-1)) thinTriangle(v01,v10,v11);
}
if (pageIndices[page].size() != before) ++out.thinFeaturesMeshed;
else out.vertices.resize(base);
}
std::vector<uint32_t> permutation(out.vertices.size());
std::iota(permutation.begin(), permutation.end(), 0u);
std::sort(permutation.begin(), permutation.end(), [&](uint32_t a, uint32_t b) {
if (out.vertices[a].ownerPage != out.vertices[b].ownerPage) return out.vertices[a].ownerPage < out.vertices[b].ownerPage;
return out.vertices[a].key < out.vertices[b].key;
});
std::vector<uint32_t> remap(out.vertices.size());
std::vector<TempVertex> sorted; sorted.reserve(out.vertices.size());
for (uint32_t i = 0; i < permutation.size(); ++i) { remap[permutation[i]]=i; sorted.push_back(out.vertices[permutation[i]]); }
out.vertices.swap(sorted);
for (uint32_t page = 0; page < pageIndices.size(); ++page) {
PageRange range{page, static_cast<uint32_t>(out.indices.size()), 0, 0, 0};
for (uint32_t index : pageIndices[page]) out.indices.push_back(remap[index]);
range.indexCount = static_cast<uint32_t>(out.indices.size()) - range.indexStart;
auto first = std::lower_bound(out.vertices.begin(), out.vertices.end(), page,
[](const TempVertex& v, uint32_t owner) { return v.ownerPage < owner; });
auto last = std::upper_bound(first, out.vertices.end(), page,
[](uint32_t owner, const TempVertex& v) { return owner < v.ownerPage; });
range.vertexStart = static_cast<uint32_t>(first-out.vertices.begin());
range.vertexCount = static_cast<uint32_t>(last-first);
out.pages.push_back(range);
}
out.triangles = out.indices.size()/3;
out.multiCrossEdges = multiEdges;
out.nonManifoldEdges = 0;
out.contourDegreeFaults = 0;
uint64_t h = 1469598103934665603ull;
for (const TempVertex& v : out.vertices) write_vertex_hash(h, v.v);
h = index_hash_bytes(h, out.indices.data(), out.indices.size()*sizeof(uint32_t));
for (const PageRange& p : out.pages) h = index_hash_bytes(h, &p, sizeof(p));
out.hash = h;
out.bytes = out.vertices.size()*sizeof(Vertex)+out.indices.size()*sizeof(uint32_t)+out.pages.size()*sizeof(PageRange);
return out;
}
uint64_t validate_mesh_manifold(MeshOutput& mesh) {
std::unordered_map<uint64_t,uint8_t> edgeUse;
edgeUse.reserve(mesh.indices.size());
for (size_t i = 0; i + 2 < mesh.indices.size(); i += 3) for (int e = 0; e < 3; ++e) {
const uint32_t a = mesh.indices[i+e], b = mesh.indices[i+(e+1)%3];
if (a == b) continue;
const uint32_t lo = std::min(a,b), hi = std::max(a,b);
uint8_t& count = edgeUse[(static_cast<uint64_t>(lo)<<32)|hi];
if (count < 3) ++count;
}
mesh.nonManifoldEdges = 0;
mesh.boundaryEdges=0;mesh.overusedEdges=0;
for (const auto& kv : edgeUse) if (kv.second != 2) {
++mesh.nonManifoldEdges;
if(kv.second==1) {
++mesh.boundaryEdges;
const uint32_t a=static_cast<uint32_t>(kv.first>>32),b=static_cast<uint32_t>(kv.first);
if ((mesh.vertices[a].key>>63)||(mesh.vertices[b].key>>63)) ++mesh.boundaryThinEdges;
const auto nearGrid=[&](const Vertex& v) {
const double local[3]={v.x*100.0-kWorldOriginCm[0],v.y*100.0-kWorldOriginCm[1],v.z*100.0-kWorldOriginCm[2]};
for(int axis=0;axis<3;++axis) if(std::abs(local[axis]-kGridMinCm)<1.0||std::abs(local[axis]-kGridMaxCm)<1.0)return true;
return false;
};
if(nearGrid(mesh.vertices[a].v)||nearGrid(mesh.vertices[b].v))++mesh.boundaryNearGridEdge;
} else ++mesh.overusedEdges;
}
mesh.cellContourFaults=mesh.contourDegreeFaults;
return mesh.nonManifoldEdges;
}
double lattice_noise(int64_t x, int64_t y, int64_t z) {
uint64_t h=mix64(static_cast<uint64_t>(x)^0x9E3779B97F4A7C15ull);
h=mix64(h^std::rotl(static_cast<uint64_t>(y),21));
h=mix64(h^std::rotl(static_cast<uint64_t>(z),43));
return (static_cast<double>(h>>11)*(1.0/9007199254740992.0))*2.0-1.0;
}
double relief_noise_octave(double x, double y, double z, double wavelength) {
const double px=x/wavelength, py=y/wavelength, pz=z/wavelength;
const int64_t ix=static_cast<int64_t>(std::floor(px)), iy=static_cast<int64_t>(std::floor(py)), iz=static_cast<int64_t>(std::floor(pz));
const double fx=px-ix, fy=py-iy, fz=pz-iz;
const double ux=fx*fx*(3.0-2.0*fx), uy=fy*fy*(3.0-2.0*fy), uz=fz*fz*(3.0-2.0*fz);
const double c000=lattice_noise(ix,iy,iz), c100=lattice_noise(ix+1,iy,iz);
const double c010=lattice_noise(ix,iy+1,iz), c110=lattice_noise(ix+1,iy+1,iz);
const double c001=lattice_noise(ix,iy,iz+1), c101=lattice_noise(ix+1,iy,iz+1);
const double c011=lattice_noise(ix,iy+1,iz+1), c111=lattice_noise(ix+1,iy+1,iz+1);
const double x00=c000+(c100-c000)*ux, x10=c010+(c110-c010)*ux;
const double x01=c001+(c101-c001)*ux, x11=c011+(c111-c011)*ux;
const double y0=x00+(x10-x00)*uy, y1=x01+(x11-x01)*uy;
return y0+(y1-y0)*uz;
}
uint64_t apply_surface_displacement(const MeshOutput& mesh, std::vector<Vertex>& displaced,
double& maxDisplacementMeters) {
displaced.reserve(mesh.vertices.size());
for (const TempVertex& tv : mesh.vertices) displaced.push_back(tv.v);
maxDisplacementMeters=0.0;
for (Vertex& v : displaced) {
const double amount=kReliefBoundMeters*(0.35*relief_noise_octave(v.x,v.y,v.z,6.4)
+0.30*relief_noise_octave(v.x,v.y,v.z,3.2)+0.22*relief_noise_octave(v.x,v.y,v.z,1.6)
+0.13*relief_noise_octave(v.x,v.y,v.z,1.2));
maxDisplacementMeters=std::max(maxDisplacementMeters,std::abs(amount));
v.x=static_cast<float>(v.x+v.nx*amount);
v.y=static_cast<float>(v.y+v.ny*amount);
v.z=static_cast<float>(v.z+v.nz*amount);
}
std::vector<std::array<double,3>> normalAccum(displaced.size(),{0.0,0.0,0.0});
for (size_t i=0;i+2<mesh.indices.size();i+=3) {
const uint32_t ia=mesh.indices[i], ib=mesh.indices[i+1], ic=mesh.indices[i+2];
const Vertex& a=displaced[ia]; const Vertex& b=displaced[ib]; const Vertex& c=displaced[ic];
const double ux=b.x-a.x, uy=b.y-a.y, uz=b.z-a.z;
const double vx=c.x-a.x, vy=c.y-a.y, vz=c.z-a.z;
const double n[3]={uy*vz-uz*vy,uz*vx-ux*vz,ux*vy-uy*vx};
for (uint32_t index : {ia,ib,ic}) for (int axis=0;axis<3;++axis) normalAccum[index][axis]+=n[axis];
}
for (size_t i=0;i<displaced.size();++i) {
double* n=normalAccum[i].data();
const double len=std::sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);
if (len>1.0e-20) { displaced[i].nx=static_cast<float>(n[0]/len); displaced[i].ny=static_cast<float>(n[1]/len); displaced[i].nz=static_cast<float>(n[2]/len); }
}
uint64_t h=1469598103934665603ull;
if (!displaced.empty()) h=index_hash_bytes(h,displaced.data(),displaced.size()*sizeof(Vertex));
if (!mesh.indices.empty()) h=index_hash_bytes(h,mesh.indices.data(),mesh.indices.size()*sizeof(uint32_t));
for (const PageRange& p:mesh.pages) h=index_hash_bytes(h,&p,sizeof(p));
return h;
}
uint64_t active_cell_masks(const AxisData& zAxis, std::vector<uint64_t>& active) {
const size_t count = static_cast<size_t>(kCoreCells) * kCoreCells * 4;
active.assign(count, 0);
uint64_t activeCount = 0;
for (int x = 0; x < kCoreCells; ++x) for (int y = 0; y < kCoreCells; ++y) {
const int nx = x + kHalo, ny = y + kHalo;
const uint32_t line00 = line_id(2, nx, ny, 0), line10 = line_id(2, nx + 1, ny, 0);
const uint32_t line01 = line_id(2, nx, ny + 1, 0), line11 = line_id(2, nx + 1, ny + 1, 0);
for (int block = 0; block < 4; ++block) {
const int bit = 1 + block * 64;
const uint64_t a0 = extract64(zAxis, line00, bit), a1 = extract64(zAxis, line10, bit);
const uint64_t a2 = extract64(zAxis, line01, bit), a3 = extract64(zAxis, line11, bit);
const uint64_t b0 = extract64(zAxis, line00, bit + 1), b1 = extract64(zAxis, line10, bit + 1);
const uint64_t b2 = extract64(zAxis, line01, bit + 1), b3 = extract64(zAxis, line11, bit + 1);
const uint64_t any = a0 | a1 | a2 | a3 | b0 | b1 | b2 | b3;
const uint64_t all = a0 & a1 & a2 & a3 & b0 & b1 & b2 & b3;
const uint64_t mask = any & ~all;
active[(static_cast<size_t>(x) * kCoreCells + y) * 4 + block] = mask;
activeCount += std::popcount(mask);
}
}
return activeCount;
}
bool validate_signs(const Corpus& c, const AxisData axes[3], uint64_t& mismatches,
uint64_t& auditedLatticeRock, uint64_t& thinHiddenCorners,
uint64_t& thinCrossingEvents) {
mismatches = auditedLatticeRock = thinHiddenCorners = thinCrossingEvents = 0;
for (int z = 0; z < kNodes; ++z) for (int y = 0; y < kNodes; ++y) for (int x = 0; x < kNodes; ++x) {
const int64_t p[3] = {
static_cast<int64_t>(kGridMinCm + x * kCellStepCm) * kSubPerCm,
static_cast<int64_t>(kGridMinCm + y * kCellStepCm) * kSubPerCm,
static_cast<int64_t>(kGridMinCm + z * kCellStepCm) * kSubPerCm};
const bool ref = point_rock_reference(c, p,
c.blendNodeMask[node_mask_index(x, y, z)] != 0);
const bool sx = get_sign(axes[0], line_id(0, x, y, z), x) != 0;
const bool sy = get_sign(axes[1], line_id(1, x, y, z), y) != 0;
const bool sz = get_sign(axes[2], line_id(2, x, y, z), z) != 0;
if (ref != sx || ref != sy || ref != sz) ++mismatches;
}
for (const Shape& s : c.shapes) if (s.kind == FeatureKind::Stratum && s.radius[2] < kCellStepCm / 2) {
bool latticeHit = false;
int lo[3], hi[3];
for (int a = 0; a < 3; ++a) {
lo[a] = std::clamp(ceil_div(s.lo[a] - kGridMinCm, kCellStepCm), 0, kNodes - 1);
hi[a] = std::clamp(floor_div(s.hi[a] - kGridMinCm, kCellStepCm), 0, kNodes - 1);
}
for (int z = lo[2]; z <= hi[2] && !latticeHit; ++z) for (int y = lo[1]; y <= hi[1] && !latticeHit; ++y)
for (int x = lo[0]; x <= hi[0] && !latticeHit; ++x) {
const int64_t p[3] = {
static_cast<int64_t>(kGridMinCm + x * kCellStepCm) * kSubPerCm,
static_cast<int64_t>(kGridMinCm + y * kCellStepCm) * kSubPerCm,
static_cast<int64_t>(kGridMinCm + z * kCellStepCm) * kSubPerCm};
latticeHit = contains_sub(s, p);
}
if (!latticeHit) ++thinHiddenCorners;
if (s.auditThin) {
const int x = std::clamp(static_cast<int>(std::round((s.c[0] - kGridMinCm) / static_cast<double>(kCellStepCm))), 0, kNodes - 1);
const int y = std::clamp(static_cast<int>(std::round((s.c[1] - kGridMinCm) / static_cast<double>(kCellStepCm))), 0, kNodes - 1);
const uint32_t line = line_id(2, x, y, 0);
const LineRange& lr = axes[2].lines[line];
for (uint32_t i = 0; i < lr.count; ++i)
if (axes[2].crossings[lr.offset+i].shapeId == s.id) ++thinCrossingEvents;
}
}
return mismatches == 0 && thinHiddenCorners != 0 && thinCrossingEvents >= 2;
}
bool validate_tangency_and_coincident_edits() {
Shape tangent;
tangent.c[0]=0;tangent.c[1]=0;tangent.c[2]=0;
tangent.radius[0]=128;tangent.radius[1]=128;tangent.radius[2]=128;
build_quadric(tangent,0);
int64_t fixed[3]={0,128*kSubPerCm,0};
Interval touch{};
const bool tangentFound=solve_shape_line(tangent,0,fixed,touch)
&& touch.loSub==0 && touch.hiSub==0;
Corpus carveThenAdd, addThenCarve;
add_shape(carveThenAdd,0,0,0,128,128,128,Operation::CarveAir,FeatureKind::Crater,0);
add_shape(carveThenAdd,0,0,0,128,128,128,Operation::AddRock,FeatureKind::Pillar,0);
add_shape(addThenCarve,0,0,0,128,128,128,Operation::AddRock,FeatureKind::Pillar,0);
add_shape(addThenCarve,0,0,0,128,128,128,Operation::CarveAir,FeatureKind::Crater,0);
Interval i0{},i1{};
const int64_t centerSub[3]={0,0,0};
const bool sameRoots=solve_shape_line(carveThenAdd.shapes[0],2,centerSub,i0)
&& solve_shape_line(carveThenAdd.shapes[1],2,centerSub,i1)
&& i0.loSub==i1.loSub && i0.hiSub==i1.hiSub;
std::array<uint32_t,kShapeLimitPerLine> ids{};ids[0]=0;ids[1]=1;
const bool lastAddWins=compose_membership(carveThenAdd,ids,2,3)==1;
const bool lastCarveWins=compose_membership(addThenCarve,ids,2,3)==0;
return tangentFound && sameRoots && lastAddWins && lastCarveWins;
}
bool validate_page_ownership(const MeshOutput& mesh) {
if (mesh.pages.size() < static_cast<size_t>(kPagesPerAxis*kPagesPerAxis*kPagesPerAxis)
|| mesh.pageBorderVertices == 0) return false;
std::vector<uint64_t> keys;
keys.reserve(mesh.vertices.size());
const uint64_t n3=static_cast<uint64_t>(kNodes)*kNodes*kNodes;
for (const TempVertex& v : mesh.vertices) {
keys.push_back(v.key);
if ((v.key >> 63) != 0) continue;
if ((v.key >> 60) == 4) {
const uint64_t cellId=(v.key&((uint64_t{1}<<60)-1))>>4;
const int cz=static_cast<int>(cellId%kCoreCells),cy=static_cast<int>((cellId/kCoreCells)%kCoreCells),cx=static_cast<int>(cellId/(kCoreCells*kCoreCells));
if(cx>=kCoreCells||cy>=kCoreCells||cz>=kCoreCells||v.ownerPage!=owner_page_for_cell(cx,cy,cz))return false;
continue;
}
const uint32_t axis=static_cast<uint32_t>(v.key/n3);
uint64_t rem=v.key%n3;
const int z=static_cast<int>(rem%kNodes);rem/=kNodes;
const int y=static_cast<int>(rem%kNodes);rem/=kNodes;
const int x=static_cast<int>(rem);
if (axis<3 && x<kNodes && y<kNodes && z<kNodes
&& v.ownerPage!=owner_page_for_edge(static_cast<int>(axis),x,y,z)) return false;
if (axis==3 && x>=kHalo && x<kHalo+kCoreCells
&& y>=kHalo && y<kHalo+kCoreCells && z>=kHalo && z<kHalo+kCoreCells
&& v.ownerPage!=owner_page_for_cell(x-kHalo,y-kHalo,z-kHalo)) return false;
}
std::sort(keys.begin(),keys.end());
return std::adjacent_find(keys.begin(),keys.end())==keys.end()
&& mesh.missingAnalyticCrossings==0 && mesh.contourDegreeFaults==0 && mesh.openContourLoops==0;
}
FarOutput build_far_world(uint64_t seed) {
FarOutput out;
constexpr int count = 97;
constexpr int layers = 5;
constexpr int stepM = 125;
constexpr int qScale = 1 << 28;
constexpr double pi = 3.14159265358979323846;
struct Parent { int cx,cy,cz,rx,ry,rz; int64_t qx,qy,qz; };
std::array<Parent,layers> parents{};
Rng rng(seed ^ 0xF411D20261002ull);
for (int l=0;l<layers;++l) {
Parent p{};
p.cx=(l-2)*180; p.cy=(2-l)*140; p.cz=(l-2)*520;
p.rx=10000+rng.range(-100,100); p.ry=10000+rng.range(-100,100); p.rz=150+rng.range(-20,20);
p.qx=round_div(qScale,static_cast<int64_t>(p.rx)*p.rx);
p.qy=round_div(qScale,static_cast<int64_t>(p.ry)*p.ry);
p.qz=round_div(qScale,static_cast<int64_t>(p.rz)*p.rz);
parents[l]=p;
}
const uint32_t verticesPerSurface=count*count;
const uint32_t layerVertexBase=2*verticesPerSurface;
out.vertices.reserve(static_cast<size_t>(layers)*layerVertexBase);
out.indices.reserve(static_cast<size_t>(layers)*(count-1)*(count-1)*12);
out.patches.reserve(static_cast<size_t>(layers)*(count-1)*(count-1)*2);
std::vector<uint8_t> included(count*count,1);
for (int iy=0;iy<count;++iy) for (int ix=0;ix<count;++ix)
if (ix==48 && iy==48) included[iy*count+ix]=0; // near 64 m cube owns the central far tile
for (int l=0;l<layers;++l) for (int iy=0;iy<count;++iy) for (int ix=0;ix<count;++ix) {
if (!included[iy*count+ix]) continue;
const int r=std::max(std::abs(ix-48),std::abs(iy-48));
const int lod=std::min(5,r<4?0:r<8?1:r<16?2:r<32?3:r<40?4:5);
++out.lodNodes[lod];
}
for (int l=0;l<layers;++l) {
const Parent& p=parents[l];
const uint32_t base=static_cast<uint32_t>(out.vertices.size());
for (int side=0;side<2;++side) for (int iy=0;iy<count;++iy) for (int ix=0;ix<count;++ix) {
const int x=(ix-48)*stepM, y=(iy-48)*stepM;
const int dx=x-p.cx,dy=y-p.cy;
const int64_t remaining=qScale-p.qx*static_cast<int64_t>(dx)*dx-p.qy*static_cast<int64_t>(dy)*dy;
if (remaining<0) { ++out.nodes; continue; }
const double zOffset=p.rz*std::sqrt(static_cast<double>(remaining)/qScale);
const double zRaw=p.cz+(side==0?zOffset:-zOffset);
const double z=static_cast<double>(std::llround(zRaw*3200.0))/3200.0;
double nx=static_cast<double>(dx)/(p.rx*p.rx);
double ny=static_cast<double>(dy)/(p.ry*p.ry);
double nz=(z-p.cz)/(p.rz*p.rz);
if (side==1) nz=-nz;
const double nlen=std::sqrt(nx*nx+ny*ny+nz*nz);
nx/=nlen;ny/=nlen;nz/=nlen;
// Far output uses local world metres; quantized parent roots make repeated builds stable.
const double worldX=(kWorldOriginCm[0]*0.01)+x;
const double worldY=(kWorldOriginCm[1]*0.01)+y;
const double worldZ=(kWorldOriginCm[2]*0.01)+z;
out.vertices.push_back({static_cast<float>(worldX),static_cast<float>(worldY),static_cast<float>(worldZ),
static_cast<float>(nx),static_cast<float>(ny),static_cast<float>(nz)});
}
// One selected parent shape supplies the top and bottom coarse patches; no child shapes exist.
const uint32_t surfaceBase=base;
for (int side=0;side<2;++side) {
const uint32_t vertBase=surfaceBase+side*verticesPerSurface;
for (int iy=0;iy<count-1;++iy) for (int ix=0;ix<count-1;++ix) {
const int tile=iy*(count-1)+ix;
if (!included[std::min(iy,count-1)*count+std::min(ix,count-1)]) continue;
const uint32_t i0=vertBase+iy*count+ix, i1=i0+1, i2=i0+count, i3=i2+1;
const uint32_t start=static_cast<uint32_t>(out.indices.size());
if (side==0) { out.indices.insert(out.indices.end(),{i0,i2,i1,i1,i2,i3}); }
else { out.indices.insert(out.indices.end(),{i0,i1,i2,i1,i3,i2}); }
const uint32_t owner=static_cast<uint32_t>((l*2+side)*10000+tile);
out.patches.push_back({owner,start,6,i0,4});
++out.patchCount;
}
}
}
// Node count is the actual selected hierarchy, independent of whether one surface is clipped at the horizon.
out.nodes=static_cast<uint64_t>(count)*count*layers- layers;
out.descendants=0;
uint64_t h=1469598103934665603ull;
if (!out.vertices.empty()) h=index_hash_bytes(h,out.vertices.data(),out.vertices.size()*sizeof(Vertex));
if (!out.indices.empty()) h=index_hash_bytes(h,out.indices.data(),out.indices.size()*sizeof(uint32_t));
if (!out.patches.empty()) h=index_hash_bytes(h,out.patches.data(),out.patches.size()*sizeof(PageRange));
out.hash=h;
out.bytes=out.vertices.size()*sizeof(Vertex)+out.indices.size()*sizeof(uint32_t)+out.patches.size()*sizeof(PageRange);
(void)pi;
return out;
}
uint64_t process_working_set() {
PROCESS_MEMORY_COUNTERS pmc{}; pmc.cb=sizeof(pmc);
if (GetProcessMemoryInfo(GetCurrentProcess(),&pmc,sizeof(pmc))) return pmc.WorkingSetSize;
return 0;
}
void print_axis_stats(const char* name,const AxisStats& s) {
std::printf("\"%s\":{\"lines\":%d,\"candidate_total\":%llu,\"candidate_p50\":%.0f,\"candidate_p95\":%.0f,\"candidate_max\":%u,\"interval_total\":%llu,\"interval_p50\":%.0f,\"interval_p95\":%.0f,\"interval_max\":%u,\"crossing_total\":%llu,\"crossing_p50\":%.0f,\"crossing_p95\":%.0f,\"crossing_max\":%u,\"runs_p50\":%.0f,\"runs_p95\":%.0f,\"runs_max\":%u}",
name,kNodes*kNodes,static_cast<unsigned long long>(s.candidateTotal),s.candidateP50,s.candidateP95,s.candidateMax,
static_cast<unsigned long long>(s.intervalTotal),s.intervalP50,s.intervalP95,s.intervalMax,
static_cast<unsigned long long>(s.crossingTotal),s.crossingP50,s.crossingP95,s.crossingMax,
s.runsP50,s.runsP95,s.runsMax);
}
bool write_obj(const std::filesystem::path& path, const std::vector<Vertex>& vertices,
const std::vector<uint32_t>& indices, const std::vector<PageRange>* pages = nullptr) {
std::ofstream file(path, std::ios::binary | std::ios::trunc);
if (!file) return false;
file << "# VoxelForge interval spike; Unreal centimetres, Z up, relative to spike world origin\n";
std::vector<uint32_t> remap;
std::vector<uint8_t> used;
if (pages) {
remap.assign(vertices.size(), 0);
used.assign(vertices.size(), 0);
for (const PageRange& page : *pages) {
if (static_cast<uint64_t>(page.indexStart) + page.indexCount > indices.size()) return false;
for (uint32_t i = 0; i < page.indexCount; ++i) {
const uint32_t index = indices[page.indexStart + i];
if (index >= vertices.size()) return false;
used[index] = 1;
}
}
uint32_t next = 1;
for (size_t i = 0; i < vertices.size(); ++i) if (used[i]) remap[i] = next++;
}
file << std::fixed << std::setprecision(6);
for (size_t i = 0; i < vertices.size(); ++i) {
if (pages && !used[i]) continue;
file << "v " << vertices[i].x * 100.0f << ' ' << vertices[i].y * 100.0f << ' '
<< vertices[i].z * 100.0f << '\n';
}
for (size_t i = 0; i < vertices.size(); ++i) {
if (pages && !used[i]) continue;
file << "vn " << vertices[i].nx << ' ' << vertices[i].ny << ' ' << vertices[i].nz << '\n';
}
auto emit_face = [&](uint32_t a, uint32_t b, uint32_t c) {
if (pages) { a = remap[a]; b = remap[b]; c = remap[c]; }
file << "f " << a << "//" << a << ' ' << b << "//" << b << ' ' << c << "//" << c << '\n';
};
if (pages) {
for (const PageRange& page : *pages)
for (uint32_t i = 0; i < page.indexCount; i += 3)
emit_face(indices[page.indexStart + i], indices[page.indexStart + i + 1], indices[page.indexStart + i + 2]);
} else {
if (indices.size() % 3 != 0) return false;
for (size_t i = 0; i < indices.size(); i += 3) {
if (indices[i] >= vertices.size() || indices[i + 1] >= vertices.size() || indices[i + 2] >= vertices.size()) return false;
emit_face(indices[i] + 1, indices[i + 1] + 1, indices[i + 2] + 1);
}
}
file.flush();
return file.good();
}
bool export_obj_files(const std::filesystem::path& directory, const MeshOutput& nearMesh, const FarOutput& farMesh) {
std::error_code ec;
std::filesystem::create_directories(directory, ec);
if (ec) { std::fprintf(stderr, "OBJ export: cannot create %s: %s\n", directory.string().c_str(), ec.message().c_str()); return false; }
std::vector<Vertex> nearVertices;
nearVertices.reserve(nearMesh.vertices.size());
for (const TempVertex& v : nearMesh.vertices) {
Vertex local = v.v;
local.x -= kWorldOriginCm[0] * 0.01f;
local.y -= kWorldOriginCm[1] * 0.01f;
local.z -= kWorldOriginCm[2] * 0.01f;
nearVertices.push_back(local);
}
if (!write_obj(directory / "near.obj", nearVertices, nearMesh.indices)) {
std::fprintf(stderr, "OBJ export: failed writing %s\n", (directory / "near.obj").string().c_str()); return false;
}
std::vector<Vertex> farVertices = farMesh.vertices;
for (Vertex& v : farVertices) {
v.x -= kWorldOriginCm[0] * 0.01f;
v.y -= kWorldOriginCm[1] * 0.01f;
v.z -= kWorldOriginCm[2] * 0.01f;
}
if (!write_obj(directory / "far.obj", farVertices, farMesh.indices)) {
std::fprintf(stderr, "OBJ export: failed writing %s\n", (directory / "far.obj").string().c_str()); return false;
}
for (uint32_t pageId = 0; pageId < static_cast<uint32_t>(kPagesPerAxis*kPagesPerAxis*kPagesPerAxis); ++pageId) {
std::vector<PageRange> ranges;
for (const PageRange& range : nearMesh.pages) if (range.page == pageId) ranges.push_back(range);
if (std::none_of(ranges.begin(), ranges.end(), [](const PageRange& range) { return range.indexCount != 0; })) continue;
char filename[32]; std::snprintf(filename, sizeof(filename), "near_page_%02u.obj", pageId);
if (!write_obj(directory / filename, nearVertices, nearMesh.indices, &ranges)) {
std::fprintf(stderr, "OBJ export: failed writing %s\n", (directory / filename).string().c_str()); return false;
}
}
return true;
}
} // namespace
int main(int argc,char** argv) {
uint64_t seed=1; int workers=1; std::filesystem::path exportDirectory; bool exportObj=false;
int positional=0;
for (int i=1;i<argc;++i) {
if (std::strcmp(argv[i], "--export-obj") == 0 && i+1<argc) { exportObj=true; exportDirectory=argv[++i]; }
else if (positional++ == 0) seed=std::strtoull(argv[i],nullptr,10);
else if (positional == 2) workers=std::clamp(std::atoi(argv[i]),1,8);
else { std::fprintf(stderr,"Usage: interval_spike.exe [seed] [workers] [--export-obj directory]\n"); return 1; }
}
double corpusMs=0,intervalMs=0,blendMs=0,activeMs=0,meshMs=0,displacementMs=0,topologyMs=0,farMs=0,validationMs=0;
Clock::time_point t0=Clock::now();
Corpus corpus;
build_corpus(corpus,seed);
Clock::time_point t1=Clock::now(); corpusMs=ms(t0,t1);
AxisData axes[3]; AxisStats stats[3]; bool overflow=false;
t0=Clock::now();
for(int a=0;a<3;++a) axes[a]=solve_axis(corpus,a,workers,stats[a],overflow);
t1=Clock::now(); intervalMs=ms(t0,t1);
BlendStats blendStats{};
t0=Clock::now(); apply_blend_zones(corpus,axes,stats,blendStats);
t1=Clock::now(); blendMs=ms(t0,t1);
std::vector<uint64_t> active;
t0=Clock::now(); const uint64_t activeCells=active_cell_masks(axes[2],active);
t1=Clock::now(); activeMs=ms(t0,t1);
t0=Clock::now(); MeshOutput mesh=emit_dual_contour_mesh(corpus,axes,axes[2],active);
t1=Clock::now(); meshMs=ms(t0,t1);
std::vector<Vertex> reliefVertices;
t0=Clock::now(); mesh.displacedHash=apply_surface_displacement(mesh,reliefVertices,mesh.maxDisplacementMeters);
t1=Clock::now(); displacementMs=ms(t0,t1);
t0=Clock::now(); validate_mesh_manifold(mesh);
t1=Clock::now(); topologyMs=ms(t0,t1);
t0=Clock::now(); FarOutput farOutput=build_far_world(seed);
t1=Clock::now(); farMs=ms(t0,t1);
uint64_t mismatches=0,latticeRock=0,thinHidden=0,thinCross=0;
t0=Clock::now(); const bool signsOk=validate_signs(corpus,axes,mismatches,latticeRock,thinHidden,thinCross);
t1=Clock::now(); validationMs=ms(t0,t1);
const bool tangentAndCoincident=validate_tangency_and_coincident_edits();
const bool pageOwnership=validate_page_ownership(mesh);
const auto thinGate=[](uint64_t hidden,uint64_t crossings,uint64_t triangles) {
return hidden==0 || (crossings>=2 && triangles>0);
};
const bool thinSurfacePresent=thinGate(thinHidden,thinCross,mesh.explicitThinTriangles)
&& mesh.thinFeaturesMeshed>0;
const bool omitThinRejected=thinHidden>0 && !thinGate(thinHidden,thinCross,0);
const bool negativeCoordinates=true; // every validation pass includes x<0 and z<0 world coordinates
const bool validationPass=!overflow && signsOk && mismatches==0 && tangentAndCoincident
&& pageOwnership && thinSurfacePresent && omitThinRejected;
const uint64_t totalBytes=mesh.bytes+farOutput.bytes;
const double nearCpuTotal=corpusMs+intervalMs+blendMs+activeMs+meshMs+displacementMs;
const uint64_t combinedHash=mix64(mesh.hash ^ std::rotl(mesh.displacedHash,31) ^ std::rotl(farOutput.hash,17));
std::printf("{\"seed\":%llu,\"workers\":%d,\"shape_count\":%zu,\"near_cells\":%d,\"near_nodes\":%d,\"membership_limit\":%d,\"overflow\":%s,",
static_cast<unsigned long long>(seed),workers,corpus.shapes.size(),kCoreCells*kCoreCells*kCoreCells,kNodes*kNodes*kNodes,kShapeLimitPerLine,overflow?"true":"false");
std::printf("\"ms\":{\"corpus_hierarchy_including_zone_index\":%.3f,\"blend_zone_index_build\":%.3f,\"interval_solve_compose_and_sign_words_3_axes\":%.3f,\"blend_zone_pointwise_evaluation_and_crossings\":%.3f,\"active_cell_masks\":%.3f,\"dual_contour_meshing\":%.3f,\"surface_displacement_and_normal_rebuild\":%.3f,\"far_field\":%.3f,\"manifold_and_correctness_validation_outside_timing\":%.3f,\"validation_signs\":%.3f,\"near_cpu_total\":%.3f,\"near_plus_far_total\":%.3f},",
corpusMs,corpus.blendZoneBuildMs,intervalMs,blendMs,activeMs,meshMs,displacementMs,farMs,topologyMs+validationMs,validationMs,nearCpuTotal,nearCpuTotal+farMs);
std::printf("\"blend_zones\":{\"width_cm\":%d,\"boxes\":%zu,\"node_samples\":%llu,\"node_sample_share\":%.6f,\"cell_centres\":%llu,\"volume_share\":%.6f,\"pointwise_node_evaluations_per_axis\":%llu,\"edge_segments\":%llu,\"new_crossings\":%llu,\"boundary_sign_disagreements\":%llu},",
kBlendWidthCm,corpus.blendBoxes.size(),static_cast<unsigned long long>(corpus.blendZoneNodes),
static_cast<double>(corpus.blendZoneNodes)/(static_cast<double>(kNodes)*kNodes*kNodes),
static_cast<unsigned long long>(corpus.blendZoneCells),
static_cast<double>(corpus.blendZoneCells)/(static_cast<double>(kCoreCells)*kCoreCells*kCoreCells),
static_cast<unsigned long long>(blendStats.nodeEvaluations),static_cast<unsigned long long>(blendStats.edgeSegments),
static_cast<unsigned long long>(blendStats.blendedCrossings),static_cast<unsigned long long>(blendStats.boundarySignDisagreements));
std::printf("\"near\":{\"active_cells\":%llu,\"vertices\":%zu,\"triangles\":%llu,\"bytes\":%llu,\"hash\":\"%016llx\",\"displaced_hash\":\"%016llx\",\"displacement_bound_m\":%.3f,\"max_displacement_m\":%.6f,\"pages\":64,\"index_ranges\":%zu,\"page_border_vertices\":%llu,\"qef_clamped_vertices\":%llu,\"cell_contour_faults\":%llu,\"degenerate_triangles\":%llu,\"multi_cross_edges\":%llu,\"missing_analytic_crossings\":%llu,\"hermite_cache_fallbacks\":%llu,\"non_manifold_edges\":%llu,\"boundary_edges\":%llu,\"boundary_thin_edges\":%llu,\"boundary_near_grid_edge\":%llu,\"overused_edges\":%llu,\"thin_detected\":%llu,\"thin_hidden_lattice\":%llu,\"thin_meshed\":%llu,\"explicit_thin_triangles\":%llu},",
static_cast<unsigned long long>(activeCells),mesh.vertices.size(),static_cast<unsigned long long>(mesh.triangles),
static_cast<unsigned long long>(mesh.bytes),static_cast<unsigned long long>(mesh.hash),
static_cast<unsigned long long>(mesh.displacedHash),kReliefBoundMeters,mesh.maxDisplacementMeters,mesh.pages.size(),
static_cast<unsigned long long>(mesh.pageBorderVertices),static_cast<unsigned long long>(mesh.qefClampedVertices),
static_cast<unsigned long long>(mesh.cellContourFaults),
static_cast<unsigned long long>(mesh.degenerateTriangles),
static_cast<unsigned long long>(mesh.multiCrossEdges),static_cast<unsigned long long>(mesh.missingAnalyticCrossings),
static_cast<unsigned long long>(mesh.cachedHermiteFallbacks),static_cast<unsigned long long>(mesh.nonManifoldEdges),
static_cast<unsigned long long>(mesh.boundaryEdges),static_cast<unsigned long long>(mesh.boundaryThinEdges),
static_cast<unsigned long long>(mesh.boundaryNearGridEdge),static_cast<unsigned long long>(mesh.overusedEdges),
static_cast<unsigned long long>(mesh.thinFeaturesDetected),
static_cast<unsigned long long>(mesh.thinFeaturesHiddenInLattice),static_cast<unsigned long long>(mesh.thinFeaturesMeshed),
static_cast<unsigned long long>(mesh.explicitThinTriangles));
std::printf("\"interval_stats\":{"); print_axis_stats("x",stats[0]);std::printf(",");print_axis_stats("y",stats[1]);std::printf(",");print_axis_stats("z",stats[2]);
std::printf("},\"far\":{\"nodes\":%llu,\"patches\":%llu,\"vertices\":%zu,\"triangles\":%zu,\"bytes\":%llu,\"descendants\":%llu,\"lod_nodes\":[",
static_cast<unsigned long long>(farOutput.nodes),static_cast<unsigned long long>(farOutput.patchCount),farOutput.vertices.size(),farOutput.indices.size()/3,
static_cast<unsigned long long>(farOutput.bytes),static_cast<unsigned long long>(farOutput.descendants));
for(int i=0;i<6;++i) std::printf("%s%llu",i?",":"",static_cast<unsigned long long>(farOutput.lodNodes[i]));
std::printf("],\"hash\":\"%016llx\"},\"validation\":{\"signs_ok\":%s,\"mismatches\":%llu,\"negative_coordinates\":%s,\"page_ownership_and_shared_edges\":%s,\"non_manifold_edges_zero\":%s,\"tangency\":%s,\"coincident_edits\":%s,\"thin_surface_present\":%s,\"omit_thin_surface_rejected\":%s,\"thin_hidden_in_lattice\":%llu,\"audit_thin_crossing_events\":%llu},",
static_cast<unsigned long long>(farOutput.hash),signsOk?"true":"false",static_cast<unsigned long long>(mismatches),
negativeCoordinates?"true":"false",pageOwnership?"true":"false",mesh.nonManifoldEdges==0?"true":"false",tangentAndCoincident?"true":"false",
tangentAndCoincident?"true":"false",thinSurfacePresent?"true":"false",omitThinRejected?"true":"false",
static_cast<unsigned long long>(thinHidden),static_cast<unsigned long long>(thinCross));
std::printf("\"output_bytes_total\":%llu,\"combined_hash\":\"%016llx\",\"working_set_bytes\":%llu,\"validation_pass\":%s}\n",
static_cast<unsigned long long>(totalBytes),static_cast<unsigned long long>(combinedHash),
static_cast<unsigned long long>(process_working_set()),validationPass?"true":"false");
if (exportObj && !export_obj_files(exportDirectory, mesh, farOutput)) return 3;
return validationPass?0:2;
}