Terrain mesh, vertex color from heightmaps
This commit is contained in:
parent
9c29567349
commit
e572269eac
5 changed files with 1400 additions and 3 deletions
535
src/3d/MeshLayer.cpp
Normal file
535
src/3d/MeshLayer.cpp
Normal file
|
|
@ -0,0 +1,535 @@
|
|||
// MeshLayer.cpp - Static 3D geometry layer implementation
|
||||
|
||||
#include "MeshLayer.h"
|
||||
#include "Shader3D.h"
|
||||
#include "../platform/GLContext.h"
|
||||
|
||||
// GL headers based on backend
|
||||
#if defined(MCRF_SDL2)
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <GLES2/gl2.h>
|
||||
#else
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
#endif
|
||||
#define MCRF_HAS_GL 1
|
||||
#elif !defined(MCRF_HEADLESS)
|
||||
#include <glad/glad.h>
|
||||
#define MCRF_HAS_GL 1
|
||||
#endif
|
||||
|
||||
namespace mcrf {
|
||||
|
||||
// =============================================================================
|
||||
// Constructor / Destructor
|
||||
// =============================================================================
|
||||
|
||||
MeshLayer::MeshLayer()
|
||||
: name_("unnamed")
|
||||
, zIndex_(0)
|
||||
, visible_(true)
|
||||
{}
|
||||
|
||||
MeshLayer::MeshLayer(const std::string& name, int zIndex)
|
||||
: name_(name)
|
||||
, zIndex_(zIndex)
|
||||
, visible_(true)
|
||||
{}
|
||||
|
||||
MeshLayer::~MeshLayer() {
|
||||
cleanupGPU();
|
||||
}
|
||||
|
||||
MeshLayer::MeshLayer(MeshLayer&& other) noexcept
|
||||
: name_(std::move(other.name_))
|
||||
, zIndex_(other.zIndex_)
|
||||
, visible_(other.visible_)
|
||||
, vertices_(std::move(other.vertices_))
|
||||
, heightData_(std::move(other.heightData_))
|
||||
, heightmapWidth_(other.heightmapWidth_)
|
||||
, heightmapHeight_(other.heightmapHeight_)
|
||||
, vbo_(other.vbo_)
|
||||
, dirty_(other.dirty_)
|
||||
, texture_(other.texture_)
|
||||
, tilesPerRow_(other.tilesPerRow_)
|
||||
, tilesPerCol_(other.tilesPerCol_)
|
||||
, modelMatrix_(other.modelMatrix_)
|
||||
{
|
||||
other.vbo_ = 0; // Prevent cleanup in moved-from object
|
||||
}
|
||||
|
||||
MeshLayer& MeshLayer::operator=(MeshLayer&& other) noexcept {
|
||||
if (this != &other) {
|
||||
cleanupGPU();
|
||||
|
||||
name_ = std::move(other.name_);
|
||||
zIndex_ = other.zIndex_;
|
||||
visible_ = other.visible_;
|
||||
vertices_ = std::move(other.vertices_);
|
||||
heightData_ = std::move(other.heightData_);
|
||||
heightmapWidth_ = other.heightmapWidth_;
|
||||
heightmapHeight_ = other.heightmapHeight_;
|
||||
vbo_ = other.vbo_;
|
||||
dirty_ = other.dirty_;
|
||||
texture_ = other.texture_;
|
||||
tilesPerRow_ = other.tilesPerRow_;
|
||||
tilesPerCol_ = other.tilesPerCol_;
|
||||
modelMatrix_ = other.modelMatrix_;
|
||||
|
||||
other.vbo_ = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Configuration
|
||||
// =============================================================================
|
||||
|
||||
void MeshLayer::setSpriteSheetLayout(int tilesPerRow, int tilesPerCol) {
|
||||
tilesPerRow_ = tilesPerRow > 0 ? tilesPerRow : 1;
|
||||
tilesPerCol_ = tilesPerCol > 0 ? tilesPerCol : 1;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Mesh Generation - HeightMap
|
||||
// =============================================================================
|
||||
|
||||
void MeshLayer::buildFromHeightmap(TCOD_heightmap_t* heightmap, float yScale, float cellSize) {
|
||||
if (!heightmap || heightmap->w < 2 || heightmap->h < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
int w = heightmap->w;
|
||||
int h = heightmap->h;
|
||||
|
||||
// Store heightmap dimensions and data for later texture range application
|
||||
heightmapWidth_ = w;
|
||||
heightmapHeight_ = h;
|
||||
heightData_.resize(w * h);
|
||||
for (int i = 0; i < w * h; i++) {
|
||||
heightData_[i] = heightmap->values[i];
|
||||
}
|
||||
|
||||
// Calculate grid vertices
|
||||
// For an NxM heightmap, we create (N-1)×(M-1) quads = 2×(N-1)×(M-1) triangles
|
||||
int numQuadsX = w - 1;
|
||||
int numQuadsZ = h - 1;
|
||||
int numTriangles = numQuadsX * numQuadsZ * 2;
|
||||
int numVertices = numTriangles * 3;
|
||||
|
||||
vertices_.clear();
|
||||
vertices_.reserve(numVertices);
|
||||
|
||||
// Generate triangles for each quad
|
||||
for (int z = 0; z < numQuadsZ; z++) {
|
||||
for (int x = 0; x < numQuadsX; x++) {
|
||||
// Get heights at quad corners (in heightmap, z is row index, x is column)
|
||||
float h00 = heightmap->values[z * w + x] * yScale;
|
||||
float h10 = heightmap->values[z * w + (x + 1)] * yScale;
|
||||
float h01 = heightmap->values[(z + 1) * w + x] * yScale;
|
||||
float h11 = heightmap->values[(z + 1) * w + (x + 1)] * yScale;
|
||||
|
||||
// World positions
|
||||
vec3 p00(x * cellSize, h00, z * cellSize);
|
||||
vec3 p10((x + 1) * cellSize, h10, z * cellSize);
|
||||
vec3 p01(x * cellSize, h01, (z + 1) * cellSize);
|
||||
vec3 p11((x + 1) * cellSize, h11, (z + 1) * cellSize);
|
||||
|
||||
// UVs (tiled across terrain, will be adjusted by applyTextureRanges)
|
||||
vec2 uv00(static_cast<float>(x), static_cast<float>(z));
|
||||
vec2 uv10(static_cast<float>(x + 1), static_cast<float>(z));
|
||||
vec2 uv01(static_cast<float>(x), static_cast<float>(z + 1));
|
||||
vec2 uv11(static_cast<float>(x + 1), static_cast<float>(z + 1));
|
||||
|
||||
// Default color (white, will be modulated by texture)
|
||||
vec4 color(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
|
||||
// Triangle 1: p00 -> p01 -> p10 (counter-clockwise from above)
|
||||
// This ensures the normal points UP (+Y) for proper backface culling
|
||||
vec3 n1 = computeFaceNormal(p00, p01, p10);
|
||||
vertices_.emplace_back(p00, uv00, n1, color);
|
||||
vertices_.emplace_back(p01, uv01, n1, color);
|
||||
vertices_.emplace_back(p10, uv10, n1, color);
|
||||
|
||||
// Triangle 2: p10 -> p01 -> p11 (counter-clockwise from above)
|
||||
vec3 n2 = computeFaceNormal(p10, p01, p11);
|
||||
vertices_.emplace_back(p10, uv10, n2, color);
|
||||
vertices_.emplace_back(p01, uv01, n2, color);
|
||||
vertices_.emplace_back(p11, uv11, n2, color);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute smooth vertex normals (average adjacent face normals)
|
||||
computeVertexNormals();
|
||||
|
||||
dirty_ = true;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Mesh Generation - Plane
|
||||
// =============================================================================
|
||||
|
||||
void MeshLayer::buildPlane(float width, float depth, float y) {
|
||||
vertices_.clear();
|
||||
vertices_.reserve(6); // 2 triangles
|
||||
|
||||
// Clear height data (plane has no height variation)
|
||||
heightData_.clear();
|
||||
heightmapWidth_ = 0;
|
||||
heightmapHeight_ = 0;
|
||||
|
||||
float halfW = width * 0.5f;
|
||||
float halfD = depth * 0.5f;
|
||||
|
||||
vec3 p00(-halfW, y, -halfD);
|
||||
vec3 p10(halfW, y, -halfD);
|
||||
vec3 p01(-halfW, y, halfD);
|
||||
vec3 p11(halfW, y, halfD);
|
||||
|
||||
vec3 normal(0, 1, 0); // Facing up
|
||||
vec4 color(1, 1, 1, 1);
|
||||
|
||||
// Triangle 1
|
||||
vertices_.emplace_back(p00, vec2(0, 0), normal, color);
|
||||
vertices_.emplace_back(p10, vec2(1, 0), normal, color);
|
||||
vertices_.emplace_back(p01, vec2(0, 1), normal, color);
|
||||
|
||||
// Triangle 2
|
||||
vertices_.emplace_back(p10, vec2(1, 0), normal, color);
|
||||
vertices_.emplace_back(p11, vec2(1, 1), normal, color);
|
||||
vertices_.emplace_back(p01, vec2(0, 1), normal, color);
|
||||
|
||||
dirty_ = true;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Texture Ranges
|
||||
// =============================================================================
|
||||
|
||||
void MeshLayer::applyTextureRanges(const std::vector<TextureRange>& ranges) {
|
||||
if (ranges.empty() || heightData_.empty() || vertices_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate tile UV size
|
||||
float tileU = 1.0f / tilesPerRow_;
|
||||
float tileV = 1.0f / tilesPerCol_;
|
||||
|
||||
// For each vertex, find its height and apply the appropriate texture
|
||||
// Vertices are stored as triangles, 6 per quad (2 triangles × 3 vertices)
|
||||
int numQuadsX = heightmapWidth_ - 1;
|
||||
int numQuadsZ = heightmapHeight_ - 1;
|
||||
|
||||
for (int z = 0; z < numQuadsZ; z++) {
|
||||
for (int x = 0; x < numQuadsX; x++) {
|
||||
int quadIndex = z * numQuadsX + x;
|
||||
int baseVertex = quadIndex * 6;
|
||||
|
||||
// Get heights at quad corners (normalized 0-1)
|
||||
float h00 = heightData_[z * heightmapWidth_ + x];
|
||||
float h10 = heightData_[z * heightmapWidth_ + (x + 1)];
|
||||
float h01 = heightData_[(z + 1) * heightmapWidth_ + x];
|
||||
float h11 = heightData_[(z + 1) * heightmapWidth_ + (x + 1)];
|
||||
|
||||
// Use average height to select texture
|
||||
float avgHeight = (h00 + h10 + h01 + h11) * 0.25f;
|
||||
|
||||
// Find matching range
|
||||
int spriteIndex = 0;
|
||||
for (const auto& range : ranges) {
|
||||
if (avgHeight >= range.minHeight && avgHeight <= range.maxHeight) {
|
||||
spriteIndex = range.spriteIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate sprite UV offset in sprite sheet
|
||||
int tileX = spriteIndex % tilesPerRow_;
|
||||
int tileY = spriteIndex / tilesPerRow_;
|
||||
float uOffset = tileX * tileU;
|
||||
float vOffset = tileY * tileV;
|
||||
|
||||
// Update UVs for all 6 vertices of this quad
|
||||
// Triangle 1: p00, p10, p01
|
||||
if (baseVertex + 5 < static_cast<int>(vertices_.size())) {
|
||||
// Local UV within quad (0-1) scaled to tile size and offset
|
||||
vertices_[baseVertex + 0].texcoord = vec2(uOffset, vOffset);
|
||||
vertices_[baseVertex + 1].texcoord = vec2(uOffset + tileU, vOffset);
|
||||
vertices_[baseVertex + 2].texcoord = vec2(uOffset, vOffset + tileV);
|
||||
|
||||
// Triangle 2: p10, p11, p01
|
||||
vertices_[baseVertex + 3].texcoord = vec2(uOffset + tileU, vOffset);
|
||||
vertices_[baseVertex + 4].texcoord = vec2(uOffset + tileU, vOffset + tileV);
|
||||
vertices_[baseVertex + 5].texcoord = vec2(uOffset, vOffset + tileV);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dirty_ = true;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Color Map
|
||||
// =============================================================================
|
||||
|
||||
void MeshLayer::applyColorMap(TCOD_heightmap_t* rMap, TCOD_heightmap_t* gMap, TCOD_heightmap_t* bMap) {
|
||||
if (!rMap || !gMap || !bMap) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vertices_.empty() || heightmapWidth_ < 2 || heightmapHeight_ < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify color maps match terrain dimensions
|
||||
if (rMap->w != heightmapWidth_ || rMap->h != heightmapHeight_ ||
|
||||
gMap->w != heightmapWidth_ || gMap->h != heightmapHeight_ ||
|
||||
bMap->w != heightmapWidth_ || bMap->h != heightmapHeight_) {
|
||||
return; // Dimension mismatch
|
||||
}
|
||||
|
||||
int numQuadsX = heightmapWidth_ - 1;
|
||||
int numQuadsZ = heightmapHeight_ - 1;
|
||||
|
||||
for (int z = 0; z < numQuadsZ; z++) {
|
||||
for (int x = 0; x < numQuadsX; x++) {
|
||||
int quadIndex = z * numQuadsX + x;
|
||||
int baseVertex = quadIndex * 6;
|
||||
|
||||
if (baseVertex + 5 >= static_cast<int>(vertices_.size())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sample RGB at each corner of the quad
|
||||
// Corner indices in heightmap
|
||||
int idx00 = z * heightmapWidth_ + x;
|
||||
int idx10 = z * heightmapWidth_ + (x + 1);
|
||||
int idx01 = (z + 1) * heightmapWidth_ + x;
|
||||
int idx11 = (z + 1) * heightmapWidth_ + (x + 1);
|
||||
|
||||
// Build colors for each corner (clamped to 0-1)
|
||||
auto clamp01 = [](float v) { return v < 0.0f ? 0.0f : (v > 1.0f ? 1.0f : v); };
|
||||
|
||||
vec4 c00(clamp01(rMap->values[idx00]), clamp01(gMap->values[idx00]), clamp01(bMap->values[idx00]), 1.0f);
|
||||
vec4 c10(clamp01(rMap->values[idx10]), clamp01(gMap->values[idx10]), clamp01(bMap->values[idx10]), 1.0f);
|
||||
vec4 c01(clamp01(rMap->values[idx01]), clamp01(gMap->values[idx01]), clamp01(bMap->values[idx01]), 1.0f);
|
||||
vec4 c11(clamp01(rMap->values[idx11]), clamp01(gMap->values[idx11]), clamp01(bMap->values[idx11]), 1.0f);
|
||||
|
||||
// Triangle 1: p00, p01, p10 (vertices 0, 1, 2)
|
||||
vertices_[baseVertex + 0].color = c00;
|
||||
vertices_[baseVertex + 1].color = c01;
|
||||
vertices_[baseVertex + 2].color = c10;
|
||||
|
||||
// Triangle 2: p10, p01, p11 (vertices 3, 4, 5)
|
||||
vertices_[baseVertex + 3].color = c10;
|
||||
vertices_[baseVertex + 4].color = c01;
|
||||
vertices_[baseVertex + 5].color = c11;
|
||||
}
|
||||
}
|
||||
|
||||
dirty_ = true;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Clear
|
||||
// =============================================================================
|
||||
|
||||
void MeshLayer::clear() {
|
||||
vertices_.clear();
|
||||
heightData_.clear();
|
||||
heightmapWidth_ = 0;
|
||||
heightmapHeight_ = 0;
|
||||
dirty_ = true;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// GPU Upload
|
||||
// =============================================================================
|
||||
|
||||
void MeshLayer::uploadToGPU() {
|
||||
#ifdef MCRF_HAS_GL
|
||||
if (!gl::isGLReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create VBO if needed
|
||||
if (vbo_ == 0) {
|
||||
glGenBuffers(1, &vbo_);
|
||||
}
|
||||
|
||||
// Upload vertex data
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||
if (!vertices_.empty()) {
|
||||
glBufferData(GL_ARRAY_BUFFER,
|
||||
vertices_.size() * sizeof(MeshVertex),
|
||||
vertices_.data(),
|
||||
GL_STATIC_DRAW);
|
||||
} else {
|
||||
glBufferData(GL_ARRAY_BUFFER, 0, nullptr, GL_STATIC_DRAW);
|
||||
}
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
dirty_ = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Rendering
|
||||
// =============================================================================
|
||||
|
||||
void MeshLayer::render(const mat4& model, const mat4& view, const mat4& projection) {
|
||||
#ifdef MCRF_HAS_GL
|
||||
if (!gl::isGLReady() || vertices_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Upload to GPU if needed
|
||||
if (dirty_ || vbo_ == 0) {
|
||||
uploadToGPU();
|
||||
}
|
||||
|
||||
if (vbo_ == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bind VBO
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||
|
||||
// Vertex format: pos(3) + texcoord(2) + normal(3) + color(4) = 12 floats = 48 bytes
|
||||
int stride = sizeof(MeshVertex);
|
||||
|
||||
// Set up vertex attributes
|
||||
glEnableVertexAttribArray(Shader3D::ATTRIB_POSITION);
|
||||
glVertexAttribPointer(Shader3D::ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE,
|
||||
stride, reinterpret_cast<void*>(offsetof(MeshVertex, position)));
|
||||
|
||||
glEnableVertexAttribArray(Shader3D::ATTRIB_TEXCOORD);
|
||||
glVertexAttribPointer(Shader3D::ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE,
|
||||
stride, reinterpret_cast<void*>(offsetof(MeshVertex, texcoord)));
|
||||
|
||||
glEnableVertexAttribArray(Shader3D::ATTRIB_NORMAL);
|
||||
glVertexAttribPointer(Shader3D::ATTRIB_NORMAL, 3, GL_FLOAT, GL_FALSE,
|
||||
stride, reinterpret_cast<void*>(offsetof(MeshVertex, normal)));
|
||||
|
||||
glEnableVertexAttribArray(Shader3D::ATTRIB_COLOR);
|
||||
glVertexAttribPointer(Shader3D::ATTRIB_COLOR, 4, GL_FLOAT, GL_FALSE,
|
||||
stride, reinterpret_cast<void*>(offsetof(MeshVertex, color)));
|
||||
|
||||
// Draw triangles
|
||||
glDrawArrays(GL_TRIANGLES, 0, static_cast<int>(vertices_.size()));
|
||||
|
||||
// Cleanup
|
||||
glDisableVertexAttribArray(Shader3D::ATTRIB_POSITION);
|
||||
glDisableVertexAttribArray(Shader3D::ATTRIB_TEXCOORD);
|
||||
glDisableVertexAttribArray(Shader3D::ATTRIB_NORMAL);
|
||||
glDisableVertexAttribArray(Shader3D::ATTRIB_COLOR);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Private Helpers
|
||||
// =============================================================================
|
||||
|
||||
void MeshLayer::cleanupGPU() {
|
||||
#ifdef MCRF_HAS_GL
|
||||
if (vbo_ != 0 && gl::isGLReady()) {
|
||||
glDeleteBuffers(1, &vbo_);
|
||||
vbo_ = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
vec3 MeshLayer::computeFaceNormal(const vec3& v0, const vec3& v1, const vec3& v2) {
|
||||
vec3 edge1 = v1 - v0;
|
||||
vec3 edge2 = v2 - v0;
|
||||
return edge1.cross(edge2).normalized();
|
||||
}
|
||||
|
||||
void MeshLayer::computeVertexNormals() {
|
||||
// For terrain mesh, we can average normals at shared positions
|
||||
// This is a simplified approach - works well for regular grids
|
||||
|
||||
if (vertices_.empty() || heightmapWidth_ < 2 || heightmapHeight_ < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a grid of accumulated normals for each heightmap point
|
||||
std::vector<vec3> accumulatedNormals(heightmapWidth_ * heightmapHeight_, vec3(0, 0, 0));
|
||||
std::vector<int> normalCounts(heightmapWidth_ * heightmapHeight_, 0);
|
||||
|
||||
// Each quad contributes to its 4 corners
|
||||
int numQuadsX = heightmapWidth_ - 1;
|
||||
int numQuadsZ = heightmapHeight_ - 1;
|
||||
|
||||
for (int z = 0; z < numQuadsZ; z++) {
|
||||
for (int x = 0; x < numQuadsX; x++) {
|
||||
int quadIndex = z * numQuadsX + x;
|
||||
int baseVertex = quadIndex * 6;
|
||||
|
||||
// Get face normals from the two triangles
|
||||
if (baseVertex + 5 < static_cast<int>(vertices_.size())) {
|
||||
vec3 n1 = vertices_[baseVertex].normal;
|
||||
vec3 n2 = vertices_[baseVertex + 3].normal;
|
||||
|
||||
// Corners: (x,z), (x+1,z), (x,z+1), (x+1,z+1)
|
||||
int idx00 = z * heightmapWidth_ + x;
|
||||
int idx10 = z * heightmapWidth_ + (x + 1);
|
||||
int idx01 = (z + 1) * heightmapWidth_ + x;
|
||||
int idx11 = (z + 1) * heightmapWidth_ + (x + 1);
|
||||
|
||||
// Triangle 1 (p00, p01, p10) contributes n1 to those corners
|
||||
accumulatedNormals[idx00] += n1;
|
||||
normalCounts[idx00]++;
|
||||
accumulatedNormals[idx01] += n1;
|
||||
normalCounts[idx01]++;
|
||||
accumulatedNormals[idx10] += n1;
|
||||
normalCounts[idx10]++;
|
||||
|
||||
// Triangle 2 (p10, p01, p11) contributes n2 to those corners
|
||||
accumulatedNormals[idx10] += n2;
|
||||
normalCounts[idx10]++;
|
||||
accumulatedNormals[idx01] += n2;
|
||||
normalCounts[idx01]++;
|
||||
accumulatedNormals[idx11] += n2;
|
||||
normalCounts[idx11]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize accumulated normals
|
||||
for (size_t i = 0; i < accumulatedNormals.size(); i++) {
|
||||
if (normalCounts[i] > 0) {
|
||||
accumulatedNormals[i] = accumulatedNormals[i].normalized();
|
||||
} else {
|
||||
accumulatedNormals[i] = vec3(0, 1, 0); // Default up
|
||||
}
|
||||
}
|
||||
|
||||
// Apply averaged normals back to vertices
|
||||
for (int z = 0; z < numQuadsZ; z++) {
|
||||
for (int x = 0; x < numQuadsX; x++) {
|
||||
int quadIndex = z * numQuadsX + x;
|
||||
int baseVertex = quadIndex * 6;
|
||||
|
||||
int idx00 = z * heightmapWidth_ + x;
|
||||
int idx10 = z * heightmapWidth_ + (x + 1);
|
||||
int idx01 = (z + 1) * heightmapWidth_ + x;
|
||||
int idx11 = (z + 1) * heightmapWidth_ + (x + 1);
|
||||
|
||||
if (baseVertex + 5 < static_cast<int>(vertices_.size())) {
|
||||
// Triangle 1: p00, p01, p10
|
||||
vertices_[baseVertex + 0].normal = accumulatedNormals[idx00];
|
||||
vertices_[baseVertex + 1].normal = accumulatedNormals[idx01];
|
||||
vertices_[baseVertex + 2].normal = accumulatedNormals[idx10];
|
||||
|
||||
// Triangle 2: p10, p01, p11
|
||||
vertices_[baseVertex + 3].normal = accumulatedNormals[idx10];
|
||||
vertices_[baseVertex + 4].normal = accumulatedNormals[idx01];
|
||||
vertices_[baseVertex + 5].normal = accumulatedNormals[idx11];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mcrf
|
||||
Loading…
Add table
Add a link
Reference in a new issue