voxel example

This commit is contained in:
John McCardle 2026-02-05 10:49:31 -05:00
commit 3e6b6a5847
10 changed files with 1691 additions and 2 deletions

171
src/3d/VoxelGrid.cpp Normal file
View file

@ -0,0 +1,171 @@
// VoxelGrid.cpp - Dense 3D voxel array implementation
// Part of McRogueFace 3D Extension - Milestones 9-10
#include "VoxelGrid.h"
#include "VoxelMesher.h"
#include "MeshLayer.h" // For MeshVertex
namespace mcrf {
// Static air material for out-of-bounds or ID=0 queries
static VoxelMaterial airMaterial{"air", sf::Color::Transparent, -1, true, 0.0f};
// =============================================================================
// Constructor
// =============================================================================
VoxelGrid::VoxelGrid(int w, int h, int d, float cellSize)
: width_(w), height_(h), depth_(d), cellSize_(cellSize),
offset_(0, 0, 0), rotation_(0.0f)
{
if (w <= 0 || h <= 0 || d <= 0) {
throw std::invalid_argument("VoxelGrid dimensions must be positive");
}
if (cellSize <= 0.0f) {
throw std::invalid_argument("VoxelGrid cell size must be positive");
}
// Allocate dense array, initialized to air (0)
size_t totalSize = static_cast<size_t>(w) * h * d;
data_.resize(totalSize, 0);
}
// =============================================================================
// Per-voxel access
// =============================================================================
bool VoxelGrid::isValid(int x, int y, int z) const {
return x >= 0 && x < width_ &&
y >= 0 && y < height_ &&
z >= 0 && z < depth_;
}
uint8_t VoxelGrid::get(int x, int y, int z) const {
if (!isValid(x, y, z)) {
return 0; // Out of bounds returns air
}
return data_[index(x, y, z)];
}
void VoxelGrid::set(int x, int y, int z, uint8_t material) {
if (!isValid(x, y, z)) {
return; // Out of bounds is no-op
}
data_[index(x, y, z)] = material;
meshDirty_ = true;
}
// =============================================================================
// Material palette
// =============================================================================
uint8_t VoxelGrid::addMaterial(const VoxelMaterial& mat) {
if (materials_.size() >= 255) {
throw std::runtime_error("Material palette full (max 255 materials)");
}
materials_.push_back(mat);
return static_cast<uint8_t>(materials_.size()); // 1-indexed
}
uint8_t VoxelGrid::addMaterial(const std::string& name, sf::Color color,
int spriteIndex, bool transparent, float pathCost) {
return addMaterial(VoxelMaterial(name, color, spriteIndex, transparent, pathCost));
}
const VoxelMaterial& VoxelGrid::getMaterial(uint8_t id) const {
if (id == 0 || id > materials_.size()) {
return airMaterial;
}
return materials_[id - 1]; // 1-indexed, so ID 1 = materials_[0]
}
// =============================================================================
// Bulk operations
// =============================================================================
void VoxelGrid::fill(uint8_t material) {
std::fill(data_.begin(), data_.end(), material);
meshDirty_ = true;
}
// =============================================================================
// Transform
// =============================================================================
mat4 VoxelGrid::getModelMatrix() const {
// Apply translation first, then rotation around Y axis
mat4 translation = mat4::translate(offset_);
mat4 rotation = mat4::rotateY(rotation_ * DEG_TO_RAD);
return translation * rotation;
}
// =============================================================================
// Statistics
// =============================================================================
size_t VoxelGrid::countNonAir() const {
size_t count = 0;
for (uint8_t v : data_) {
if (v != 0) {
count++;
}
}
return count;
}
size_t VoxelGrid::countMaterial(uint8_t material) const {
size_t count = 0;
for (uint8_t v : data_) {
if (v == material) {
count++;
}
}
return count;
}
// =============================================================================
// fillBox (Milestone 10)
// =============================================================================
void VoxelGrid::fillBox(int x0, int y0, int z0, int x1, int y1, int z1, uint8_t material) {
// Ensure proper ordering (min to max)
if (x0 > x1) std::swap(x0, x1);
if (y0 > y1) std::swap(y0, y1);
if (z0 > z1) std::swap(z0, z1);
// Clamp to valid range
x0 = std::max(0, std::min(x0, width_ - 1));
x1 = std::max(0, std::min(x1, width_ - 1));
y0 = std::max(0, std::min(y0, height_ - 1));
y1 = std::max(0, std::min(y1, height_ - 1));
z0 = std::max(0, std::min(z0, depth_ - 1));
z1 = std::max(0, std::min(z1, depth_ - 1));
for (int z = z0; z <= z1; z++) {
for (int y = y0; y <= y1; y++) {
for (int x = x0; x <= x1; x++) {
data_[index(x, y, z)] = material;
}
}
}
meshDirty_ = true;
}
// =============================================================================
// Mesh Caching (Milestone 10)
// =============================================================================
const std::vector<MeshVertex>& VoxelGrid::getVertices() const {
if (meshDirty_) {
rebuildMesh();
}
return cachedVertices_;
}
void VoxelGrid::rebuildMesh() const {
cachedVertices_.clear();
VoxelMesher::generateMesh(*this, cachedVertices_);
meshDirty_ = false;
}
} // namespace mcrf