feat(editor): auto-paint by slope for natural cliff texturing

- Auto-Paint by Slope: paints rock texture on steep terrain surfaces
  with configurable slope threshold (0.1 - 0.9)
- Uses per-vertex normal Z component to detect steepness
- Alpha blending based on slope gradient for smooth transitions
- Workflow: sculpt terrain → recalc normals → auto-paint slope → rock
  appears naturally on cliffs while flat areas keep their biome texture
This commit is contained in:
Kelsi 2026-05-05 07:19:05 -07:00
parent 8aee357a34
commit 59c6dab2b3
3 changed files with 53 additions and 0 deletions

View file

@ -194,6 +194,45 @@ void TexturePainter::autoPaintByHeight(const std::vector<HeightBand>& bands) {
}
}
void TexturePainter::autoPaintBySlope(float slopeThreshold, const std::string& steepTexture) {
if (!terrain_) return;
uint32_t steepTexId = ensureTextureInList(steepTexture);
for (int ci = 0; ci < 256; ci++) {
auto& chunk = terrain_->chunks[ci];
if (!chunk.hasHeightMap() || chunk.layers.empty()) continue;
// Compute average slope from normals
float maxSlope = 0.0f;
for (int v = 0; v < 145; v++) {
float nz = static_cast<float>(chunk.normals[v * 3 + 2]) / 127.0f;
float slope = 1.0f - std::abs(nz);
maxSlope = std::max(maxSlope, slope);
}
if (maxSlope > slopeThreshold) {
// Add steep texture as a layer
int layerIdx = ensureLayerOnChunk(ci, steepTexId);
if (layerIdx > 0) {
size_t off = chunk.layers[layerIdx].offsetMCAL;
if (off + 4096 <= chunk.alphaMap.size()) {
for (int ty = 0; ty < 64; ty++) {
for (int tx = 0; tx < 64; tx++) {
// Sample slope at this texel
int vi = (ty / 8) * 17 + (tx / 8);
if (vi >= 145) vi = 144;
float nz = static_cast<float>(chunk.normals[vi * 3 + 2]) / 127.0f;
float slope = 1.0f - std::abs(nz);
float alpha = std::clamp((slope - slopeThreshold * 0.5f) / (1.0f - slopeThreshold * 0.5f), 0.0f, 1.0f);
chunk.alphaMap[off + ty * 64 + tx] = static_cast<uint8_t>(alpha * 255.0f);
}
}
}
}
}
}
}
std::vector<int> TexturePainter::erase(const glm::vec3& center, float radius,
float strength, float falloff) {
if (!terrain_ || activeTexture_.empty()) return {};