// Weltgenerierung: Terrain, Biome, Hoehlen, Wasser, Baeume. // Arbeitet direkt auf Chunk-Block-Arrays. Baeume koennen chunk-uebergreifend // in Nachbarn schreiben (world.setGen / world.getGen). import { CHUNK_SIZE, CHUNK_HEIGHT, BLOCK, BIOME } from './constants.js'; import { SEA_LEVEL } from './config.js'; import { noise2DTerrain, noise2DDetail, noise2DTemp, noise2DHumid, noise3DCave, fbm2, } from './noise.js'; import { hash2 } from './rng.js'; // Biom aus Temperatur & Feuchtigkeit export function getBiome(wx, wz) { const temp = noise2DTemp(wx * 0.004, wz * 0.004); // -1..1 const humid = noise2DHumid(wx * 0.004 + 50, wz * 0.004 - 50); if (temp > 0.35 && humid < 0.0) return BIOME.DESERT; if (humid > 0.05) return BIOME.FOREST; return BIOME.PLAINS; } // Terrainhoehe (Ganzzahl) pro Weltsaeule in Abhaengigkeit vom Biom export function terrainHeight(wx, wz, biome) { // Basis-Huegel let n = fbm2(noise2DTerrain, wx * 0.0125, wz * 0.0125, 4, 2.0, 0.5); // -1..1 let height; if (biome === BIOME.DESERT) { const dunes = noise2DDetail(wx * 0.03, wz * 0.03) * 0.5; height = SEA_LEVEL + 1 + (n * 0.5 + dunes) * 6; // flach } else if (biome === BIOME.FOREST) { height = SEA_LEVEL + 4 + n * 10; } else { // Plains mit gelegentlichen Huegeln const hill = fbm2(noise2DTerrain, wx * 0.005 + 100, wz * 0.005 - 100, 2, 2.0, 0.5); height = SEA_LEVEL + 2 + n * 7 + hill * 6; } return Math.max(2, Math.min(CHUNK_HEIGHT - 6, Math.round(height))); } // Setzt einen Baum (Stamm + Blattkrone) an (wx, groundY, wz) function placeTree(world, wx, groundY, wz) { const h = 4 + (Math.floor(hash2(wx + 7, wz - 3) * 3)); // 4..6 for (let i = 1; i <= h; i++) { world.setGen(wx, groundY + i, wz, BLOCK.WOOD); } const topY = groundY + h; // Blattkrone for (let dy = -2; dy <= 1; dy++) { const radius = dy <= -1 ? 2 : 1; for (let dx = -radius; dx <= radius; dx++) { for (let dz = -radius; dz <= radius; dz++) { if (dx === 0 && dz === 0 && dy <= 0) continue; // Stammbereich freilassen // Ecken etwas ausduennen const dist = Math.abs(dx) + Math.abs(dz) + Math.abs(dy); if (dist > 3) continue; if ((Math.abs(dx) === radius && Math.abs(dz) === radius) && hash2(wx + dx * 13, wz + dz * 17) < 0.5) continue; const ly = topY + dy; if (ly < 0 || ly >= CHUNK_HEIGHT) continue; if (world.getGen(wx + dx, ly, wz + dz) === BLOCK.AIR) { world.setGen(wx + dx, ly, wz + dz, BLOCK.LEAVES); } } } } // Spitze if (world.getGen(wx, topY + 1, wz) === BLOCK.AIR) { world.setGen(wx, topY + 1, wz, BLOCK.LEAVES); } } // Fuellt Block-Daten eines Chunks (ohne Meshing). export function generateChunk(world, chunk) { const baseX = chunk.cx * CHUNK_SIZE; const baseZ = chunk.cz * CHUNK_SIZE; for (let lx = 0; lx < CHUNK_SIZE; lx++) { for (let lz = 0; lz < CHUNK_SIZE; lz++) { const wx = baseX + lx; const wz = baseZ + lz; const biome = getBiome(wx, wz); const surface = terrainHeight(wx, wz, biome); for (let y = 0; y <= surface; y++) { let block = BLOCK.STONE; if (y === 0) { block = BLOCK.BEDROCK; } else if (y === surface) { block = (biome === BIOME.DESERT || surface <= SEA_LEVEL + 1) ? BLOCK.SAND : BLOCK.GRASS; } else if (y >= surface - 3) { block = (biome === BIOME.DESERT || surface <= SEA_LEVEL + 1) ? BLOCK.SAND : BLOCK.DIRT; } else { block = BLOCK.STONE; } // Hoehlen aushoehlen (nicht ganz unten, nicht an der Oberflaeche) if (y > 2 && y < surface - 1) { const c = noise3DCave(wx * 0.055, y * 0.09, wz * 0.055); if (c > 0.62) block = BLOCK.AIR; } if (block !== BLOCK.AIR) chunk.set(lx, y, lz, block); } // Wasser auffuellen bis Meereshoehe if (surface < SEA_LEVEL) { for (let y = surface + 1; y <= SEA_LEVEL; y++) { if (chunk.get(lx, y, lz) === BLOCK.AIR) chunk.set(lx, y, lz, BLOCK.WATER); } } } } // Baeume (nur in Wald/Plains, oberhalb Wasser, deterministisch) for (let lx = 0; lx < CHUNK_SIZE; lx++) { for (let lz = 0; lz < CHUNK_SIZE; lz++) { const wx = baseX + lx; const wz = baseZ + lz; const biome = getBiome(wx, wz); if (biome === BIOME.DESERT) continue; const treeChance = biome === BIOME.FOREST ? 0.028 : 0.006; if (hash2(wx * 3 + 1, wz * 5 + 2) < treeChance) { // Bodenflaeche finden let gy = -1; for (let y = CHUNK_HEIGHT - 1; y >= 0; y--) { const b = world.getGen(wx, y, wz); if (b === BLOCK.GRASS) { gy = y; break; } if (b !== BLOCK.AIR && b !== BLOCK.LEAVES && b !== BLOCK.WATER) break; } if (gy > SEA_LEVEL) placeTree(world, wx, gy, wz); } } } }