#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include #include #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 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::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 signs; std::vector lines; std::vector 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 vertices; std::vector indices; std::vector 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 ids; }; struct BlendBox { int32_t lo[3]{}, hi[3]{}; }; struct ProjectionBins { std::vector ids[kBucketCount * kBucketCount]; }; struct Corpus { std::vector shapes; Bin3D bins3d[kBucketCount * kBucketCount * kBucketCount]; Bin3D blendBins3d[kBucketCount * kBucketCount * kBucketCount]; ProjectionBins projections[3]; std::vector blendBoxes; std::vector blendNodeMask; std::vector 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 crossings; }; struct FarOutput { std::vector vertices; std::vector indices; std::vector patches; std::array 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(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(mix64(state) >> 32); } int range(int lo, int hi) { return lo + static_cast(next() % static_cast(hi - lo + 1)); } bool chance(int n, int d) { return static_cast(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(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((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(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(kNodes) * kNodes; std::vector> 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}<(z) * kNodes + y], nlo[0], nhi[0]); } c.blendNodeMask.assign(static_cast(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(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(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, 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(std::llround((value - kGridMinCm) / static_cast(kCellStepCm))); return kGridMinCm + node * kCellStepCm; }; auto isAirBeforeStratum = [&](int x, int y, int z) { const int64_t p[3] = {static_cast(x)*kSubPerCm, static_cast(y)*kSubPerCm, static_cast(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(s.lo[a])*kSubPerCm && p[a]<=static_cast(s.hi[a])*kSubPerCm; if (!insideAabb) continue; const int64_t d[3] = {p[0]-static_cast(s.c[0])*kSubPerCm, p[1]-static_cast(s.c[1])*kSubPerCm, p[2]-static_cast(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{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(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(s.c[0]) * kSubPerCm, pSub[1] - static_cast(s.c[1]) * kSubPerCm, pSub[2] - static_cast(s.c[2]) * kSubPerCm}; const int64_t q = eval_quad(s, d); const double q2 = std::max(0.0, static_cast(q) / static_cast(kQScale * kSubPerCm * kSubPerCm)); const double radius = static_cast(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 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(nodeIndex % kNodes); const int y = static_cast((nodeIndex / kNodes) % kNodes); const int z = static_cast(nodeIndex / (kNodes * kNodes)); uint8_t& marked = c.blendNodeMask[nodeIndex]; if (!marked) continue; const int64_t p[3] = { static_cast(kGridMinCm + x * kCellStepCm) * kSubPerCm, static_cast(kGridMinCm + y * kCellStepCm) * kSubPerCm, static_cast(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(s.lo[axis]-kBlendWidthCm)*kSubPerCm && p[axis]<=static_cast(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(nodeIndex)); } std::vector cellMask(static_cast(kCoreCells) * kCoreCells * kCoreCells, 0); for (uint32_t nodeIndex : c.blendNodeIndices) { const int x = static_cast(nodeIndex % kNodes); const int y = static_cast((nodeIndex / kNodes) % kNodes); const int z = static_cast(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(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(s.c[0]) * kSubPerCm, pSub[1] - static_cast(s.c[1]) * kSubPerCm, pSub[2] - static_cast(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(s.lo[a]) * kSubPerCm || pSub[a] > static_cast(s.hi[a]) * kSubPerCm) return false; } return true; } bool point_rock(const Corpus& c, const int64_t pSub[3]) { const int xcm = static_cast(floor_div(static_cast(pSub[0]), static_cast(kSubPerCm))); const int ycm = static_cast(floor_div(static_cast(pSub[1]), static_cast(kSubPerCm))); const int zcm = static_cast(floor_div(static_cast(pSub[2]), static_cast(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(floor_div(static_cast(pSub[0]), static_cast(kSubPerCm))); const int ycm = static_cast(floor_div(static_cast(pSub[1]), static_cast(kSubPerCm))); const int zcm = static_cast(floor_div(static_cast(pSub[2]), static_cast(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(s.lo[axis] - kBlendWidthCm) * kSubPerCm && pSub[axis] <= static_cast(s.hi[axis] + kBlendWidthCm) * kSubPerCm; if (!insideExpandedAabb) continue; int64_t d[3] = {pSub[0] - static_cast(s.c[0]) * kSubPerCm, pSub[1] - static_cast(s.c[1]) * kSubPerCm, pSub[2] - static_cast(s.c[2]) * kSubPerCm}; const double q2 = std::max(0.0, static_cast(eval_quad(s, d)) / static_cast(kQScale * kSubPerCm * kSubPerCm)); const double radius = static_cast(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(kBlendWidthCm)) : smooth_min(field, -phi, static_cast(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(floor_div(static_cast(pSub[0]), static_cast(kSubPerCm))); const int ycm = static_cast(floor_div(static_cast(pSub[1]), static_cast(kSubPerCm))); const int zcm = static_cast(floor_div(static_cast(pSub[2]), static_cast(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(s.lo[a]-kBlendWidthCm)*kSubPerCm && pSub[a]<=static_cast(s.hi[a]+kBlendWidthCm)*kSubPerCm; if (!inside) continue; const double d[3] = {pSub[0]/static_cast(kSubPerCm)-s.c[0], pSub[1]/static_cast(kSubPerCm)-s.c[1], pSub[2]/static_cast(kSubPerCm)-s.c[2]}; int64_t dSub[3] = {pSub[0]-static_cast(s.c[0])*kSubPerCm, pSub[1]-static_cast(s.c[1])*kSubPerCm, pSub[2]-static_cast(s.c[2])*kSubPerCm}; const double q2=std::max(0.0,static_cast(eval_quad(s,dSub))/static_cast(kQScale*kSubPerCm*kSubPerCm)); const double root=std::sqrt(q2); const double radius=static_cast(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(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 ? bfield; if (take) { field=b; for (int a=0;a<3;++a) gradient[a]=bg[a]; } continue; } const double k=static_cast(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 ? bfield; 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(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(s.lo[axis] - pad) * kSubPerCm && pSub[axis] <= static_cast(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(floor_div(static_cast(pSub[0]), static_cast(kSubPerCm))); const int ycm = static_cast(floor_div(static_cast(pSub[1]), static_cast(kSubPerCm))); const int zcm = static_cast(floor_div(static_cast(pSub[2]), static_cast(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(s.lo[axis] - kBlendWidthCm) * kSubPerCm && pSub[axis] <= static_cast(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(s.c[0]) * kSubPerCm, p[1] - static_cast(s.c[1]) * kSubPerCm, p[2] - static_cast(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(s.c[b]) * kSubPerCm; const int64_t db = fixedSub[dAxis] - static_cast(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(s.c[axis]) * kSubPerCm + floor_div64(-B, 2 * A); const int64_t minSub = static_cast(s.lo[a]) * kSubPerCm; const int64_t maxSub = static_cast(s.hi[a]) * kSubPerCm; int64_t center = std::clamp(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(kGridMinCm) * kSubPerCm; const int64_t stepSub = static_cast(kCellStepCm) * kSubPerCm; int firstSample = static_cast(ceil_div(static_cast(first - baseSub), static_cast(stepSub))); int lastSample = static_cast(floor_div(static_cast(last - baseSub), static_cast(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& 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& fn) { std::vector pool; pool.reserve(workers > 1 ? workers : 0); const size_t chunk = (count + static_cast(workers) - 1) / static_cast(workers); for (int w = 0; w < workers; ++w) { const size_t begin = std::min(count, static_cast(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(z * kNodes + y); // X varies if (axis == 1) return static_cast(x * kNodes + z); // Y varies return static_cast(y * kNodes + x); // Z varies } void decode_line(int axis, uint32_t line, int& a, int& b) { (void)axis; a = static_cast(line % kNodes); b = static_cast(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(lineCount) * kWordsPerLine, 0); result.lines.resize(lineCount); std::vector local(static_cast(workers)); std::atomic lineOverflow{false}; std::vector> 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(kGridMinCm) * kSubPerCm; const int64_t domainMaxSub = static_cast(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 ids{}; std::array intervals{}; std::array valid{}; std::array enters{}, exits{}; std::array lineWords{}; struct Ev { int64_t q; uint8_t bit; uint8_t enter; }; std::array events{}; for (size_t lineIndex = begin; lineIndex < end; ++lineIndex) { int p0, p1; decode_line(axis, static_cast(lineIndex), p0, p1); std::array 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(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(localCm[axis1]) * kSubPerCm; fixedSub[axis2] = static_cast(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(i), 1}; const int64_t exitQ = in.hiSub + 1; if (exitQ > domainMinSub && exitQ <= domainMaxSub + 1) events[eventCount++] = {exitQ, static_cast(i), 0}; } istats.push_back(static_cast(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(lineIndex) * kWordsPerLine); rstats.push_back(static_cast(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(kGridMinCm) * 32 && p32 <= static_cast(kGridMaxCm) * 32 && outCross.size() < std::numeric_limits::max()) outCross.push_back({static_cast(p32), feature, static_cast(beforeRock), static_cast(afterRock)}); } e = next; } const size_t lineCrossCount = outCross.size() - lineCrossStart; xstats.push_back(static_cast(std::min(lineCrossCount, 65535))); result.lines[lineIndex].candidates = static_cast(candidateCount); result.lines[lineIndex].primitiveIntervals = static_cast(intervalCount); result.lines[lineIndex].occupancyRuns = static_cast(runCount); result.lines[lineIndex].count = static_cast(std::min(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 workerBase(static_cast(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(lineCount) + workers - 1) / workers; for (int w = 0; w < workers; ++w) { const size_t begin = std::min(static_cast(lineCount), static_cast(w) * chunk); const size_t end = std::min(static_cast(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(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 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(0.50 * (values.size() - 1)); const size_t i95 = static_cast(0.95 * (values.size() - 1)); p50 = values[i50]; p95 = values[i95]; }; std::vector 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(stats.intervalMax, 0); return result; } uint64_t get_sign(const AxisData& axis, uint32_t line, int sample) { return (axis.signs[static_cast(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(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(kGridMinCm) * kSubPerCm; const int64_t stepSub = static_cast(kCellStepCm) * kSubPerCm; std::vector blendSigns(c.blendNodeMask.size(), 0); std::array,3> affectedLines; for (auto& v : affectedLines) v.assign(static_cast(kNodes) * kNodes, 0); for (uint32_t rawIndex : c.blendNodeIndices) { const size_t index = rawIndex; const int x = static_cast(rawIndex % kNodes); const int y = static_cast((rawIndex / kNodes) % kNodes); const int z = static_cast(rawIndex / (kNodes * kNodes)); const int64_t p[3] = { static_cast(kGridMinCm + x * kCellStepCm) * kSubPerCm, static_cast(kGridMinCm + y * kCellStepCm) * kSubPerCm, static_cast(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 oldCrossings = std::move(data.crossings); std::vector rebuilt; rebuilt.reserve(oldCrossings.size() + 4096); std::vector 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(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(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(startSub * 2); const int32_t end32 = static_cast(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(kGridMinCm + node[0] * kCellStepCm) * kSubPerCm, static_cast(kGridMinCm + node[1] * kCellStepCm) * kSubPerCm, static_cast(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(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(stepSub); } int64_t p32 = std::llround(rootSub * 2.0); p32=std::clamp(p32,static_cast(start32)+1,static_cast(end32)-1); if (p32 < std::numeric_limits::min() || p32 > std::numeric_limits::max()) continue; rebuilt.push_back({static_cast(p32), kBlendCrossingShape, static_cast(beforeRock), static_cast(!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(std::min(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(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(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((cell[2] * kPagesPerAxis + cell[1]) * kPagesPerAxis + cell[0]); } uint64_t edge_key(int axis, int x, int y, int z) { return (((static_cast(axis) * kNodes + static_cast(x)) * kNodes + static_cast(y)) * kNodes + static_cast(z)); } struct EdgeDesc { int axis, x, y, z, c0, c1; }; constexpr std::array 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(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(local[0]) * kSubPerCm, static_cast(local[1]) * kSubPerCm, static_cast(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(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(outN,3)) v *= inv; return; } const Shape& s = corpus.shapes[c.shapeId]; double d[3] = {static_cast(local[0] - s.c[0]), static_cast(local[1] - s.c[1]), static_cast(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(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(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& active, int workers) { (void)workers; MeshOutput out; std::unordered_map edgeToVertex; edgeToVertex.reserve(700000); out.pages.reserve(kPagesPerAxis * kPagesPerAxis * kPagesPerAxis); std::array cornerSigns{}; std::array 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(kSurfaceSubPerCm); p[e.axis] = static_cast(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(world[0] * 0.01), static_cast(world[1] * 0.01), static_cast(world[2] * 0.01), static_cast(n[0]), static_cast(n[1]), static_cast(n[2])}; const uint32_t owner = owner_page_for_edge(e.axis, ex, ey, ez); const uint32_t index = static_cast(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(kGridMinCm + x * kCellStepCm) * kSubPerCm, static_cast(kGridMinCm + y * kCellStepCm) * kSubPerCm, static_cast(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((pz * kPagesPerAxis + py) * kPagesPerAxis + px); const uint32_t start = static_cast(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(ix) * kCoreCells + iy) * 4 + zb]; while (mask) { const unsigned bz = std::countr_zero(mask); mask &= mask - 1; const int iz = zb * 64 + static_cast(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,12> adj{}; std::array 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(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 visited{}; for (int startEdge = 0; startEdge < 12; ++startEdge) { if (!edgeCross[startEdge] || visited[startEdge] || degree[startEdge] != 2) continue; std::vector 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(out.indices.size() - start), static_cast(vstart), static_cast(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(out.indices.size()); const uint32_t base = static_cast(out.vertices.size()); std::vector> localPoints; std::vector> 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(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(s.id) << 32) | (static_cast(r) << 16) | static_cast(a); out.vertices.push_back({thinKey, page, {static_cast(wx),static_cast(wy),static_cast(wz), static_cast(n[0]),static_cast(n[1]),static_cast(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(out.indices.size() - start), base, static_cast(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 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 remap(out.vertices.size()); std::vector 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(first - out.vertices.begin()); p.vertexCount = static_cast(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((static_cast(x) * kCoreCells + y) * kCoreCells + z); } uint32_t owner_page_for_cell(int x, int y, int z) { return static_cast(((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& active) { MeshOutput out; const size_t estimated = 300000; out.vertices.reserve(estimated); struct CellTopology { std::array edgeVertex; CellTopology() { edgeVertex.fill(std::numeric_limits::max()); } }; std::unordered_map cellTopology; cellTopology.reserve(estimated); std::array, kPagesPerAxis * kPagesPerAxis * kPagesPerAxis> pageIndices; uint64_t multiEdges = 0; struct Hermite { Crossing crossing{}; double normal[3]{}; }; std::unordered_map hermites; hermites.reserve(axes[0].crossings.size()+axes[1].crossings.size()+axes[2].crossings.size()); const int64_t domainStart32 = static_cast(kGridMinCm) * kSurfaceSubPerCm; const int64_t edgeSpan32 = static_cast(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(floor_div64(static_cast(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(kSurfaceSubPerCm); localCm[axis] = static_cast(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& 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(kSurfaceSubPerCm); const double* n=h.normal; double hp[3]={static_cast(kGridMinCm+ex*kCellStepCm-(kGridMinCm+cellX*kCellStepCm)), static_cast(kGridMinCm+ey*kCellStepCm-(kGridMinCm+cellY*kCellStepCm)), static_cast(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(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(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(component&15)); const uint32_t vertex=static_cast(out.vertices.size()); out.vertices.push_back({key,page,{static_cast(wx),static_cast(wy),static_cast(wz), static_cast(normalSum[0]/nlen),static_cast(normalSum[1]/nlen),static_cast(normalSum[2]/nlen)}}); for(int ei:loop)topology.edgeVertex[ei]=vertex; if((ix>0&&ix%kPageCells==0)||(ix+10&&iy%kPageCells==0)||(iy+10&&iz%kPageCells==0)||(iz+1(ix) * kCoreCells + iy) * 4 + block]; while (mask) { const unsigned bit=std::countr_zero(mask);mask&=mask-1; const int iz=block*64+static_cast(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]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(kSurfaceSubPerCm); int localCm[3]={kGridMinCm+ex*kCellStepCm,kGridMinCm+ey*kCellStepCm,kGridMinCm+ez*kCellStepCm};localCm[e.axis]=static_cast(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,12> adj{};std::array degree{}; auto connect=[&](int a,int b){if(degree[a]<2&°ree[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(kGridMinCm+cellX*kCellStepCm)*kSubPerCm, static_cast(kGridMinCm+cellY*kCellStepCm)*kSubPerCm, static_cast(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 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 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::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,4> cells, std::array 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::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 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(floor_div64(static_cast(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(kGridMinCm+x*kCellStepCm)*kSubPerCm, static_cast(kGridMinCm+y*kCellStepCm)*kSubPerCm, static_cast(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(out.vertices.size()); std::vector> 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(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(s.id) << 32) | (static_cast(r) << 16) | static_cast(a); out.vertices.push_back({key, page, {static_cast((kWorldOriginCm[0]+s.c[0]+localP[0])*0.01), static_cast((kWorldOriginCm[1]+s.c[1]+localP[1])*0.01), static_cast((kWorldOriginCm[2]+s.c[2]+localP[2])*0.01), static_cast(n[0]),static_cast(n[1]),static_cast(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 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 remap(out.vertices.size()); std::vector 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(out.indices.size()), 0, 0, 0}; for (uint32_t index : pageIndices[page]) out.indices.push_back(remap[index]); range.indexCount = static_cast(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(first-out.vertices.begin()); range.vertexCount = static_cast(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 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(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(kv.first>>32),b=static_cast(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(x)^0x9E3779B97F4A7C15ull); h=mix64(h^std::rotl(static_cast(y),21)); h=mix64(h^std::rotl(static_cast(z),43)); return (static_cast(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(std::floor(px)), iy=static_cast(std::floor(py)), iz=static_cast(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& 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(v.x+v.nx*amount); v.y=static_cast(v.y+v.ny*amount); v.z=static_cast(v.z+v.nz*amount); } std::vector> normalAccum(displaced.size(),{0.0,0.0,0.0}); for (size_t i=0;i+21.0e-20) { displaced[i].nx=static_cast(n[0]/len); displaced[i].ny=static_cast(n[1]/len); displaced[i].nz=static_cast(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& active) { const size_t count = static_cast(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(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(kGridMinCm + x * kCellStepCm) * kSubPerCm, static_cast(kGridMinCm + y * kCellStepCm) * kSubPerCm, static_cast(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(kGridMinCm + x * kCellStepCm) * kSubPerCm, static_cast(kGridMinCm + y * kCellStepCm) * kSubPerCm, static_cast(kGridMinCm + z * kCellStepCm) * kSubPerCm}; latticeHit = contains_sub(s, p); } if (!latticeHit) ++thinHiddenCorners; if (s.auditThin) { const int x = std::clamp(static_cast(std::round((s.c[0] - kGridMinCm) / static_cast(kCellStepCm))), 0, kNodes - 1); const int y = std::clamp(static_cast(std::round((s.c[1] - kGridMinCm) / static_cast(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 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(kPagesPerAxis*kPagesPerAxis*kPagesPerAxis) || mesh.pageBorderVertices == 0) return false; std::vector keys; keys.reserve(mesh.vertices.size()); const uint64_t n3=static_cast(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(cellId%kCoreCells),cy=static_cast((cellId/kCoreCells)%kCoreCells),cx=static_cast(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(v.key/n3); uint64_t rem=v.key%n3; const int z=static_cast(rem%kNodes);rem/=kNodes; const int y=static_cast(rem%kNodes);rem/=kNodes; const int x=static_cast(rem); if (axis<3 && x(axis),x,y,z)) return false; if (axis==3 && x>=kHalo && x=kHalo && y=kHalo && z parents{}; Rng rng(seed ^ 0xF411D20261002ull); for (int l=0;l(p.rx)*p.rx); p.qy=round_div(qScale,static_cast(p.ry)*p.ry); p.qz=round_div(qScale,static_cast(p.rz)*p.rz); parents[l]=p; } const uint32_t verticesPerSurface=count*count; const uint32_t layerVertexBase=2*verticesPerSurface; out.vertices.reserve(static_cast(layers)*layerVertexBase); out.indices.reserve(static_cast(layers)*(count-1)*(count-1)*12); out.patches.reserve(static_cast(layers)*(count-1)*(count-1)*2); std::vector included(count*count,1); for (int iy=0;iy(out.vertices.size()); for (int side=0;side<2;++side) for (int iy=0;iy(dx)*dx-p.qy*static_cast(dy)*dy; if (remaining<0) { ++out.nodes; continue; } const double zOffset=p.rz*std::sqrt(static_cast(remaining)/qScale); const double zRaw=p.cz+(side==0?zOffset:-zOffset); const double z=static_cast(std::llround(zRaw*3200.0))/3200.0; double nx=static_cast(dx)/(p.rx*p.rx); double ny=static_cast(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(worldX),static_cast(worldY),static_cast(worldZ), static_cast(nx),static_cast(ny),static_cast(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(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((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(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(s.candidateTotal),s.candidateP50,s.candidateP95,s.candidateMax, static_cast(s.intervalTotal),s.intervalP50,s.intervalP95,s.intervalMax, static_cast(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& vertices, const std::vector& indices, const std::vector* 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 remap; std::vector used; if (pages) { remap.assign(vertices.size(), 0); used.assign(vertices.size(), 0); for (const PageRange& page : *pages) { if (static_cast(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 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 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(kPagesPerAxis*kPagesPerAxis*kPagesPerAxis); ++pageId) { std::vector 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 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 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(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(corpus.blendZoneNodes), static_cast(corpus.blendZoneNodes)/(static_cast(kNodes)*kNodes*kNodes), static_cast(corpus.blendZoneCells), static_cast(corpus.blendZoneCells)/(static_cast(kCoreCells)*kCoreCells*kCoreCells), static_cast(blendStats.nodeEvaluations),static_cast(blendStats.edgeSegments), static_cast(blendStats.blendedCrossings),static_cast(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(activeCells),mesh.vertices.size(),static_cast(mesh.triangles), static_cast(mesh.bytes),static_cast(mesh.hash), static_cast(mesh.displacedHash),kReliefBoundMeters,mesh.maxDisplacementMeters,mesh.pages.size(), static_cast(mesh.pageBorderVertices),static_cast(mesh.qefClampedVertices), static_cast(mesh.cellContourFaults), static_cast(mesh.degenerateTriangles), static_cast(mesh.multiCrossEdges),static_cast(mesh.missingAnalyticCrossings), static_cast(mesh.cachedHermiteFallbacks),static_cast(mesh.nonManifoldEdges), static_cast(mesh.boundaryEdges),static_cast(mesh.boundaryThinEdges), static_cast(mesh.boundaryNearGridEdge),static_cast(mesh.overusedEdges), static_cast(mesh.thinFeaturesDetected), static_cast(mesh.thinFeaturesHiddenInLattice),static_cast(mesh.thinFeaturesMeshed), static_cast(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(farOutput.nodes),static_cast(farOutput.patchCount),farOutput.vertices.size(),farOutput.indices.size()/3, static_cast(farOutput.bytes),static_cast(farOutput.descendants)); for(int i=0;i<6;++i) std::printf("%s%llu",i?",":"",static_cast(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(farOutput.hash),signsOk?"true":"false",static_cast(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(thinHidden),static_cast(thinCross)); std::printf("\"output_bytes_total\":%llu,\"combined_hash\":\"%016llx\",\"working_set_bytes\":%llu,\"validation_pass\":%s}\n", static_cast(totalBytes),static_cast(combinedHash), static_cast(process_working_set()),validationPass?"true":"false"); if (exportObj && !export_obj_files(exportDirectory, mesh, farOutput)) return 3; return validationPass?0:2; }