Stone: add corner fill triangles at adjacent open edges and cap triangles at strip terminaisons. Grass: replace bevel strips with tuft-based grass blades — clusters of 3-9 curved double-sided blades with per-tuft height/lean personality and hash-driven placement (quadratic inset 0-0.30 from edge). Vegetation PS uses half-Lambert wrap lighting + translucency for soft stylized shading (inspired by Airborn Trees). Stone keeps classic Lambert.
544 lines
23 KiB
C++
544 lines
23 KiB
C++
#include "TopingSystem.h"
|
||
#include "VoxelWorld.h"
|
||
#include <cmath>
|
||
#include <cstring>
|
||
|
||
namespace voxel {
|
||
|
||
static constexpr float PI = 3.14159265f;
|
||
|
||
// ── Edge definitions for +Y face ────────────────────────────────
|
||
// Each edge sits on one side of the unit square [0,1] (the XZ plane at y=1).
|
||
// The bevel strip runs along the edge, with a wedge cross-section
|
||
// rising from the voxel face (y=1) to a peak (y=1+h) and sloping
|
||
// inward by width w.
|
||
//
|
||
// Cross-section (looking along the strip):
|
||
//
|
||
// peak (edge, 1+h)
|
||
// /|
|
||
// / |
|
||
// / | slope face (visible from above)
|
||
// / |
|
||
// / | outer wall (visible from the side)
|
||
// / |
|
||
// inner outer
|
||
// (1-w,1) (edge,1)
|
||
//
|
||
struct EdgeDef {
|
||
float sx, sz; // strip start point (on the voxel face)
|
||
float ex, ez; // strip end point
|
||
float ix, iz; // inward direction (unit, perpendicular to strip)
|
||
float nx, nz; // outer wall normal (points outward)
|
||
};
|
||
|
||
static const EdgeDef kEdges[4] = {
|
||
// sx sz ex ez ix iz nx nz
|
||
{ 1.0f, 0.0f, 1.0f, 1.0f,-1.0f, 0.0f, 1.0f, 0.0f }, // bit 0: +X edge
|
||
{ 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,-1.0f, 0.0f }, // bit 1: -X edge
|
||
{ 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,-1.0f, 0.0f, 1.0f }, // bit 2: +Z edge
|
||
{ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,-1.0f }, // bit 3: -Z edge
|
||
};
|
||
|
||
// ── Corner definitions ───────────────────────────────────────────
|
||
// Each corner of the +Y face is shared by two edges. When BOTH edges
|
||
// are open (bits unset), a corner fill triangle closes the gap between
|
||
// the two bevel strips. When only one edge is open, a cap triangle
|
||
// closes the strip end.
|
||
struct CornerDef {
|
||
float cx, cz; // corner position in [0,1]^2
|
||
int bitA, bitB; // edge bits that share this corner
|
||
};
|
||
|
||
static const CornerDef kCorners[4] = {
|
||
{ 1.0f, 0.0f, 0, 3 }, // +X start / -Z end
|
||
{ 1.0f, 1.0f, 0, 2 }, // +X end / +Z end
|
||
{ 0.0f, 1.0f, 1, 2 }, // -X end / +Z start
|
||
{ 0.0f, 0.0f, 1, 3 }, // -X start / -Z start
|
||
};
|
||
|
||
// For each edge: which other edge shares its start/end corner
|
||
static const int kStartNeighbor[4] = { 3, 3, 1, 1 };
|
||
static const int kEndNeighbor[4] = { 2, 2, 0, 0 };
|
||
|
||
// ── Helper: emit one triangle with auto-corrected winding ───────
|
||
// Compares the geometric normal (from vertex cross product) to the
|
||
// desired shading normal. If they disagree, swaps B and C to flip
|
||
// the winding so the triangle is front-facing (CW in Wicked Engine).
|
||
static void emitTri(std::vector<TopingVertex>& v,
|
||
float ax, float ay, float az,
|
||
float bx, float by, float bz,
|
||
float cx, float cy, float cz,
|
||
float nx, float ny, float nz)
|
||
{
|
||
// Geometric normal = cross(AB, AC)
|
||
float abx = bx - ax, aby = by - ay, abz = bz - az;
|
||
float acx = cx - ax, acy = cy - ay, acz = cz - az;
|
||
float gnx = aby * acz - abz * acy;
|
||
float gny = abz * acx - abx * acz;
|
||
float gnz = abx * acy - aby * acx;
|
||
|
||
// If geometric normal disagrees with desired normal, swap B<->C
|
||
if (gnx * nx + gny * ny + gnz * nz > 0.0f) {
|
||
v.push_back({ ax, ay, az, nx, ny, nz });
|
||
v.push_back({ cx, cy, cz, nx, ny, nz });
|
||
v.push_back({ bx, by, bz, nx, ny, nz });
|
||
} else {
|
||
v.push_back({ ax, ay, az, nx, ny, nz });
|
||
v.push_back({ bx, by, bz, nx, ny, nz });
|
||
v.push_back({ cx, cy, cz, nx, ny, nz });
|
||
}
|
||
}
|
||
|
||
// ── Deterministic hash for pseudo-random blade variety ───────────
|
||
// Returns a value in [0,1) from integer inputs. Golden ratio based.
|
||
static float hashF(int a, int b, int c) {
|
||
float x = (float)(a * 127 + b * 311 + c * 523) * 0.6180339887f;
|
||
return x - floorf(x);
|
||
}
|
||
|
||
// ── Emit a single grass blade (2 segments, double-sided) ────────
|
||
// The blade is a tapered ribbon curving outward from the voxel face.
|
||
// 2 segments give curvature; double-sided for backface-culled PSO.
|
||
//
|
||
// midLeanRatio controls the curvature profile:
|
||
// low (0.05-0.15): mostly straight bottom, sharp curve at top
|
||
// high (0.30-0.50): curves early from base, droopy look
|
||
//
|
||
static void emitBlade(std::vector<TopingVertex>& verts,
|
||
float px, float pz, // base position on voxel face (y=1)
|
||
float outX, float outZ, // outward direction from edge (normalized)
|
||
float height, float baseW,
|
||
float lean, float angleDeg,
|
||
float midLeanRatio) // 0.0-0.5, curvature shape
|
||
{
|
||
// Rotate outward direction by angle to get lean direction
|
||
float angleRad = angleDeg * PI / 180.0f;
|
||
float ca = cosf(angleRad), sa = sinf(angleRad);
|
||
float lx = outX * ca - outZ * sa;
|
||
float lz = outX * sa + outZ * ca;
|
||
|
||
// Width direction (perpendicular to lean in XZ plane)
|
||
float wx = -lz, wz = lx;
|
||
|
||
// 3 cross-sections with variable curvature
|
||
struct Section { float x, y, z, hw; };
|
||
Section secs[3] = {
|
||
{ px, 1.0f, pz, baseW * 0.50f },
|
||
{ px + lean * midLeanRatio * lx, 1.0f + height * 0.5f, pz + lean * midLeanRatio * lz, baseW * 0.32f },
|
||
{ px + lean * lx, 1.0f + height, pz + lean * lz, baseW * 0.08f },
|
||
};
|
||
|
||
for (int s = 0; s < 2; s++) {
|
||
const auto& s0 = secs[s];
|
||
const auto& s1 = secs[s + 1];
|
||
|
||
float l0x = s0.x - s0.hw * wx, l0z = s0.z - s0.hw * wz;
|
||
float r0x = s0.x + s0.hw * wx, r0z = s0.z + s0.hw * wz;
|
||
float l1x = s1.x - s1.hw * wx, l1z = s1.z - s1.hw * wz;
|
||
float r1x = s1.x + s1.hw * wx, r1z = s1.z + s1.hw * wz;
|
||
|
||
float tx = s1.x - s0.x, ty = s1.y - s0.y, tz = s1.z - s0.z;
|
||
float nx = ty * wz;
|
||
float ny = tz * wx - tx * wz;
|
||
float nz = -ty * wx;
|
||
float nlen = sqrtf(nx * nx + ny * ny + nz * nz);
|
||
if (nlen > 1e-6f) { nx /= nlen; ny /= nlen; nz /= nlen; }
|
||
else { nx = lx; ny = 0; nz = lz; }
|
||
|
||
// Front face
|
||
emitTri(verts, l0x, s0.y, l0z, r0x, s0.y, r0z, l1x, s1.y, l1z, nx, ny, nz);
|
||
emitTri(verts, r0x, s0.y, r0z, r1x, s1.y, r1z, l1x, s1.y, l1z, nx, ny, nz);
|
||
|
||
// Back face (flipped normal — emitTri auto-corrects winding)
|
||
emitTri(verts, l0x, s0.y, l0z, r0x, s0.y, r0z, l1x, s1.y, l1z, -nx, -ny, -nz);
|
||
emitTri(verts, r0x, s0.y, r0z, r1x, s1.y, r1z, l1x, s1.y, l1z, -nx, -ny, -nz);
|
||
}
|
||
}
|
||
|
||
// ═════════════════════════════════════════════════════════════════
|
||
// TopingSystem implementation
|
||
// ═════════════════════════════════════════════════════════════════
|
||
|
||
void TopingSystem::initialize() {
|
||
registerDefs();
|
||
generateMeshes();
|
||
}
|
||
|
||
// ── Register toping types ───────────────────────────────────────
|
||
void TopingSystem::registerDefs() {
|
||
defs_.clear();
|
||
|
||
// Type 0: Stone bevel — clean angular ridge along open edges
|
||
{
|
||
TopingDef def{};
|
||
def.materialID = 3;
|
||
def.face = FACE_POS_Y;
|
||
def.priority = 0;
|
||
def.height = 0.06f;
|
||
def.width = 0.12f;
|
||
def.segments = 1;
|
||
defs_.push_back(def);
|
||
}
|
||
|
||
// Type 1: Grass blades — individual grass blades along open edges
|
||
{
|
||
TopingDef def{};
|
||
def.materialID = 1;
|
||
def.face = FACE_POS_Y;
|
||
def.priority = 1;
|
||
def.height = 0.20f; // reference height (blade heights vary per preset)
|
||
def.width = 0.10f; // reference inset
|
||
def.segments = 2; // blade segments (curvature)
|
||
defs_.push_back(def);
|
||
}
|
||
}
|
||
|
||
// ── Generate all 16 mesh variants per def ───────────────────────
|
||
void TopingSystem::generateMeshes() {
|
||
vertices_.clear();
|
||
for (auto& def : defs_) {
|
||
for (int bitmask = 0; bitmask < 16; bitmask++) {
|
||
generateVariant(def, (uint8_t)bitmask);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ── Dispatch to material-specific generation ─────────────────────
|
||
void TopingSystem::generateVariant(TopingDef& def, uint8_t bitmask) {
|
||
const uint32_t startOffset = (uint32_t)vertices_.size();
|
||
|
||
if (def.materialID == 3)
|
||
generateStoneVariant(def, bitmask);
|
||
else
|
||
generateGrassVariant(def, bitmask);
|
||
|
||
const uint32_t count = (uint32_t)vertices_.size() - startOffset;
|
||
def.variants[bitmask] = { startOffset, count };
|
||
}
|
||
|
||
// ═════════════════════════════════════════════════════════════════
|
||
// Stone bevel generation
|
||
// ═════════════════════════════════════════════════════════════════
|
||
// Features:
|
||
// 1. Bevel strips along each open edge (outer wall + slope)
|
||
// 2. Corner fill triangles where two adjacent open edges meet
|
||
// 3. Cap triangles where a strip end meets a connected edge
|
||
//
|
||
void TopingSystem::generateStoneVariant(TopingDef& def, uint8_t bitmask) {
|
||
const float h = def.height;
|
||
const float w = def.width;
|
||
|
||
// ── 1. Bevel strips for open edges ──────────────────────────
|
||
for (int edge = 0; edge < 4; edge++) {
|
||
if (bitmask & (1 << edge)) continue;
|
||
|
||
const EdgeDef& e = kEdges[edge];
|
||
const float dx = e.ex - e.sx;
|
||
const float dz = e.ez - e.sz;
|
||
|
||
// Outer wall (vertical, facing outward)
|
||
emitTri(vertices_,
|
||
e.sx, 1.0f + h, e.sz, e.ex, 1.0f + h, e.ez, e.sx, 1.0f, e.sz,
|
||
e.nx, 0.0f, e.nz);
|
||
emitTri(vertices_,
|
||
e.ex, 1.0f + h, e.ez, e.ex, 1.0f, e.ez, e.sx, 1.0f, e.sz,
|
||
e.nx, 0.0f, e.nz);
|
||
|
||
// Slope (facing inward + up)
|
||
float slopeLen = sqrtf(h * h + w * w);
|
||
if (slopeLen < 1e-4f) slopeLen = 1.0f;
|
||
float cnx = e.ix * (h / slopeLen);
|
||
float cny = w / slopeLen;
|
||
float cnz = e.iz * (h / slopeLen);
|
||
|
||
float inSx = e.sx + w * e.ix, inSz = e.sz + w * e.iz;
|
||
float inEx = e.ex + w * e.ix, inEz = e.ez + w * e.iz;
|
||
|
||
emitTri(vertices_,
|
||
e.sx, 1.0f + h, e.sz, inSx, 1.0f, inSz, e.ex, 1.0f + h, e.ez,
|
||
cnx, cny, cnz);
|
||
emitTri(vertices_,
|
||
e.ex, 1.0f + h, e.ez, inSx, 1.0f, inSz, inEx, 1.0f, inEz,
|
||
cnx, cny, cnz);
|
||
|
||
// ── 3. Caps at strip endpoints ──────────────────────────
|
||
// At start: if the other edge sharing this corner is CONNECTED
|
||
int startNeighbor = kStartNeighbor[edge];
|
||
if (bitmask & (1 << startNeighbor)) {
|
||
// Cap triangle at strip start, facing -stripDir
|
||
float capNx = -dx, capNz = -dz;
|
||
float innerX = e.sx + w * e.ix;
|
||
float innerZ = e.sz + w * e.iz;
|
||
emitTri(vertices_,
|
||
e.sx, 1.0f, e.sz, e.sx, 1.0f + h, e.sz, innerX, 1.0f, innerZ,
|
||
capNx, 0.0f, capNz);
|
||
}
|
||
|
||
// At end: if the other edge sharing this corner is CONNECTED
|
||
int endNeighbor = kEndNeighbor[edge];
|
||
if (bitmask & (1 << endNeighbor)) {
|
||
// Cap triangle at strip end, facing +stripDir
|
||
float capNx = dx, capNz = dz;
|
||
float innerX = e.ex + w * e.ix;
|
||
float innerZ = e.ez + w * e.iz;
|
||
emitTri(vertices_,
|
||
e.ex, 1.0f, e.ez, e.ex, 1.0f + h, e.ez, innerX, 1.0f, innerZ,
|
||
capNx, 0.0f, capNz);
|
||
}
|
||
}
|
||
|
||
// ── 2. Corner fills where two adjacent open edges meet ──────
|
||
for (int c = 0; c < 4; c++) {
|
||
const auto& corner = kCorners[c];
|
||
// Both edges open → fill the slope gap at the corner
|
||
if ((bitmask & (1 << corner.bitA)) == 0 &&
|
||
(bitmask & (1 << corner.bitB)) == 0)
|
||
{
|
||
const EdgeDef& eA = kEdges[corner.bitA];
|
||
const EdgeDef& eB = kEdges[corner.bitB];
|
||
|
||
// Peak at corner (shared by both edges)
|
||
float peakX = corner.cx, peakZ = corner.cz;
|
||
|
||
// Inner points from each edge's inward direction
|
||
float innerAx = corner.cx + w * eA.ix;
|
||
float innerAz = corner.cz + w * eA.iz;
|
||
float innerBx = corner.cx + w * eB.ix;
|
||
float innerBz = corner.cz + w * eB.iz;
|
||
|
||
// Slope normal: compute from cross product, ensure upward
|
||
float e1x = innerAx - peakX, e1y = -h, e1z = innerAz - peakZ;
|
||
float e2x = innerBx - peakX, e2y = -h, e2z = innerBz - peakZ;
|
||
float fnx = e1y * e2z - e1z * e2y;
|
||
float fny = e1z * e2x - e1x * e2z;
|
||
float fnz = e1x * e2y - e1y * e2x;
|
||
if (fny < 0) { fnx = -fnx; fny = -fny; fnz = -fnz; }
|
||
float fnlen = sqrtf(fnx * fnx + fny * fny + fnz * fnz);
|
||
if (fnlen > 1e-6f) { fnx /= fnlen; fny /= fnlen; fnz /= fnlen; }
|
||
else { fnx = 0; fny = 1; fnz = 0; }
|
||
|
||
emitTri(vertices_,
|
||
peakX, 1.0f + h, peakZ,
|
||
innerAx, 1.0f, innerAz,
|
||
innerBx, 1.0f, innerBz,
|
||
fnx, fny, fnz);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ═════════════════════════════════════════════════════════════════
|
||
// Grass blade generation — tuft-based
|
||
// ═════════════════════════════════════════════════════════════════
|
||
// Grass is generated as clusters ("tufts") of blades that fan out
|
||
// from a shared base point. Each blade within a tuft has unique
|
||
// height, width, orientation, curvature, and lean — all derived
|
||
// deterministically from hash functions for reproducibility.
|
||
//
|
||
// Tuft placement along open edges; extra tufts at open corners.
|
||
// Density: fewer open edges → denser tufts per edge (hedge effect).
|
||
//
|
||
void TopingSystem::generateGrassVariant(TopingDef& def, uint8_t bitmask) {
|
||
// Count open edges
|
||
int openEdges = 0;
|
||
for (int b = 0; b < 4; b++)
|
||
if (!(bitmask & (1 << b))) openEdges++;
|
||
|
||
if (openEdges == 0) return;
|
||
|
||
// Tuft count per edge (more open edges → fewer tufts each)
|
||
int tuftsPerEdge;
|
||
switch (openEdges) {
|
||
case 1: tuftsPerEdge = 7; break;
|
||
case 2: tuftsPerEdge = 5; break;
|
||
case 3: tuftsPerEdge = 4; break;
|
||
default: tuftsPerEdge = 4; break;
|
||
}
|
||
|
||
// ── 1. Tufts along open edges ───────────────────────────────
|
||
for (int edge = 0; edge < 4; edge++) {
|
||
if (bitmask & (1 << edge)) continue;
|
||
|
||
const EdgeDef& e = kEdges[edge];
|
||
const float dx = e.ex - e.sx;
|
||
const float dz = e.ez - e.sz;
|
||
|
||
for (int ti = 0; ti < tuftsPerEdge; ti++) {
|
||
// ── Tuft CENTER position ─────────────────────────────
|
||
// Along edge: fully random
|
||
float t = hashF(edge * 7 + 1, ti * 13 + 3, 99);
|
||
t = 0.03f + t * 0.94f;
|
||
|
||
// Distance from edge: 0.0 (flush) to 0.45 (near center)
|
||
// Use squared hash to bias toward edge while allowing far tufts
|
||
float edgeDistHash = hashF(edge, ti, 44);
|
||
float tuftInset = edgeDistHash * edgeDistHash * 0.30f; // quadratic bias
|
||
|
||
float tuftCenterX = e.sx + t * dx + tuftInset * e.ix;
|
||
float tuftCenterZ = e.sz + t * dz + tuftInset * e.iz;
|
||
|
||
// ── Per-tuft personality ─────────────────────────────
|
||
float tuftHeightScale = 0.20f + hashF(edge, ti, 77) * 0.80f;
|
||
float tuftLeanScale = 0.3f + hashF(edge, ti, 55) * 1.5f;
|
||
int bladesInTuft = 3 + (int)(hashF(edge, ti, 33) * 6.99f);
|
||
|
||
// Emit blades tightly clustered around tuft center
|
||
for (int bi = 0; bi < bladesInTuft; bi++) {
|
||
float h1 = hashF(edge, ti, bi * 3 + 0);
|
||
float h2 = hashF(edge, ti, bi * 3 + 1);
|
||
float h3 = hashF(edge, ti, bi * 3 + 2);
|
||
float h4 = hashF(edge + 4, ti, bi * 3 + 0);
|
||
float h5 = hashF(edge + 4, ti, bi * 3 + 1);
|
||
|
||
// Fan angle
|
||
float spreadAngle = 55.0f;
|
||
float fanT = (bladesInTuft > 1)
|
||
? (float)bi / (float)(bladesInTuft - 1)
|
||
: 0.5f;
|
||
float angle = (fanT - 0.5f) * 2.0f * spreadAngle;
|
||
angle += (h1 - 0.5f) * 20.0f;
|
||
|
||
// Height: per-blade × per-tuft
|
||
float bladeHeight = (0.06f + h2 * 0.28f) * tuftHeightScale;
|
||
|
||
float baseWidth = 0.030f + h3 * 0.030f;
|
||
float lean = (0.03f + h4 * 0.12f) * tuftLeanScale;
|
||
float midLean = 0.08f + h5 * 0.35f;
|
||
|
||
// Tight scatter around tuft center (±0.03 — clustered)
|
||
float h6 = hashF(edge + 8, ti, bi * 3 + 2);
|
||
float offX = (h1 - 0.5f) * 0.06f;
|
||
float offZ = (h6 - 0.5f) * 0.06f;
|
||
|
||
float bx = tuftCenterX + offX;
|
||
float bz = tuftCenterZ + offZ;
|
||
|
||
emitBlade(vertices_, bx, bz, e.nx, e.nz,
|
||
bladeHeight, baseWidth, lean, angle, midLean);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ── 2. Corner tufts where two adjacent open edges meet ──────
|
||
for (int c = 0; c < 4; c++) {
|
||
const auto& corner = kCorners[c];
|
||
if ((bitmask & (1 << corner.bitA)) != 0 ||
|
||
(bitmask & (1 << corner.bitB)) != 0) continue;
|
||
|
||
const EdgeDef& eA = kEdges[corner.bitA];
|
||
const EdgeDef& eB = kEdges[corner.bitB];
|
||
|
||
// Diagonal outward direction (bisector of two edge normals)
|
||
float diagX = eA.nx + eB.nx;
|
||
float diagZ = eA.nz + eB.nz;
|
||
float diagLen = sqrtf(diagX * diagX + diagZ * diagZ);
|
||
if (diagLen > 1e-6f) { diagX /= diagLen; diagZ /= diagLen; }
|
||
|
||
// Corner tuft: cluster filling the diagonal gap
|
||
int cornerBlades = 4 + (int)(hashF(c + 10, 0, 33) * 5.99f);
|
||
// Corner tuft inset: 0.05 to 0.35 diagonally inward
|
||
float cDistHash = hashF(c + 10, 0, 44);
|
||
float cornerInset = 0.05f + cDistHash * 0.30f;
|
||
float cbx = corner.cx + cornerInset * (eA.ix + eB.ix);
|
||
float cbz = corner.cz + cornerInset * (eA.iz + eB.iz);
|
||
|
||
float cornerHeightScale = 0.25f + hashF(c + 10, 0, 77) * 0.75f;
|
||
float cornerLeanScale = 0.3f + hashF(c + 10, 0, 55) * 1.5f;
|
||
|
||
for (int bi = 0; bi < cornerBlades; bi++) {
|
||
float h1 = hashF(c + 10, bi, 0);
|
||
float h2 = hashF(c + 10, bi, 1);
|
||
float h3 = hashF(c + 10, bi, 2);
|
||
float h4 = hashF(c + 10, bi, 3);
|
||
float h5 = hashF(c + 10, bi, 4);
|
||
|
||
// Wide fan across the diagonal
|
||
float fanT = (float)bi / (float)(cornerBlades - 1);
|
||
float angle = (fanT - 0.5f) * 2.0f * 70.0f;
|
||
angle += (h1 - 0.5f) * 15.0f;
|
||
|
||
float height = (0.10f + h2 * 0.22f) * cornerHeightScale;
|
||
float baseWidth = 0.030f + h3 * 0.025f;
|
||
float lean = (0.04f + h4 * 0.10f) * cornerLeanScale;
|
||
float midLean = 0.10f + h5 * 0.30f;
|
||
|
||
// Tight scatter around corner tuft center (clustered)
|
||
float h6 = hashF(c + 10, bi, 5);
|
||
float offX = (h1 - 0.5f) * 0.06f;
|
||
float offZ = (h6 - 0.5f) * 0.06f;
|
||
|
||
emitBlade(vertices_, cbx + offX, cbz + offZ, diagX, diagZ,
|
||
height, baseWidth, lean, angle, midLean);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ── Collect toping instances from the world ─────────────────────
|
||
// Scans every exposed voxel face that matches a registered TopingDef,
|
||
// computes the 4-bit adjacency bitmask, and emits a TopingInstance.
|
||
//
|
||
// Currently only supports FACE_POS_Y (top face). For other faces,
|
||
// the adjacency directions would need to be adapted to the face plane.
|
||
void TopingSystem::collectInstances(const VoxelWorld& world) {
|
||
instances_.clear();
|
||
|
||
// Quick lookup: material -> toping def index (-1 if none)
|
||
int8_t matToDef[256];
|
||
memset(matToDef, -1, sizeof(matToDef));
|
||
for (size_t i = 0; i < defs_.size(); i++) {
|
||
matToDef[defs_[i].materialID] = (int8_t)i;
|
||
}
|
||
|
||
world.forEachChunk([&](const ChunkPos& cpos, const Chunk& chunk) {
|
||
for (int z = 0; z < CHUNK_SIZE; z++) {
|
||
for (int y = 0; y < CHUNK_SIZE; y++) {
|
||
for (int x = 0; x < CHUNK_SIZE; x++) {
|
||
const VoxelData& v = chunk.at(x, y, z);
|
||
if (v.isEmpty()) continue;
|
||
|
||
const uint8_t mat = v.getMaterialID();
|
||
const int8_t defIdx = matToDef[mat];
|
||
if (defIdx < 0) continue;
|
||
|
||
const TopingDef& def = defs_[defIdx];
|
||
|
||
// World coordinates
|
||
const int wx = cpos.x * CHUNK_SIZE + x;
|
||
const int wy = cpos.y * CHUNK_SIZE + y;
|
||
const int wz = cpos.z * CHUNK_SIZE + z;
|
||
|
||
// Check if the target face is exposed
|
||
if (def.face == FACE_POS_Y) {
|
||
if (!world.getVoxel(wx, wy + 1, wz).isEmpty()) continue;
|
||
|
||
// Compute 4-bit adjacency bitmask
|
||
uint8_t adj = 0;
|
||
const uint8_t myPriority = def.priority;
|
||
|
||
auto checkNeighbor = [&](int nx, int nz) -> bool {
|
||
uint8_t nMat = world.getVoxel(nx, wy, nz).getMaterialID();
|
||
if (nMat == 0) return false;
|
||
if (!world.getVoxel(nx, wy + 1, nz).isEmpty()) return false;
|
||
if (nMat == mat) return true;
|
||
int8_t nDefIdx = matToDef[nMat];
|
||
if (nDefIdx >= 0 && defs_[nDefIdx].priority >= myPriority) return true;
|
||
return false;
|
||
};
|
||
|
||
if (checkNeighbor(wx + 1, wz)) adj |= 1; // +X
|
||
if (checkNeighbor(wx - 1, wz)) adj |= 2; // -X
|
||
if (checkNeighbor(wx, wz + 1)) adj |= 4; // +Z
|
||
if (checkNeighbor(wx, wz - 1)) adj |= 8; // -Z
|
||
|
||
instances_.push_back({
|
||
(float)wx, (float)wy, (float)wz,
|
||
(uint16_t)defIdx, adj
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
} // namespace voxel
|