Files
minecraft-cloneandClaude f009789c42 feat: shadows, baked vertex AO, post-processing + central config.js
Visuals:
- PCFSoftShadowMap (2048²), directional light follows player with a tight
  shadow frustum (SHADOW_AREA) for high texel density; opak blocks cast+receive,
  water receives.
- Baked vertex ambient occlusion ("smooth lighting"): per-vertex solid-neighbor
  lookup (0fps algorithm) written into a color attribute, with flip-triangulation
  to avoid brightness seams across quad diagonals.
- EffectComposer: RenderPass -> UnrealBloomPass (subtle) -> OutputPass with
  ACES filmic tone-mapping + sRGB. HalfFloat render target (anti-banding) + MSAA.

Controls:
- Sprint moved from Ctrl to Shift (Ctrl is problematic in browsers). Shift in
  fly mode still means "descend" (no conflict, sprint only applies when walking).

config.js:
- New central js/config.js for all tweakable values (world, performance,
  renderer/tone-mapping, sky & fog, camera, lighting, shadows, bloom, AO,
  player physics, mouse sensitivity, reach) with SHADOWS_ENABLED / BLOOM_ENABLED
  feature toggles. Structural engine constants stay in constants.js.
- SEA_LEVEL / WORLD_SEED moved constants.js -> config.js (consumers rewired).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-21 00:46:03 +02:00

132 lines
4.8 KiB
JavaScript

// 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);
}
}
}
}