feat: Implement procedural terrain generation with hills, lakes, and vegetation

This commit is contained in:
Kacper Kostka (aider) 2025-04-04 12:05:41 +02:00
parent 3f4f8bc09c
commit a5702a210f

184
script.js
View File

@ -53,6 +53,7 @@ let chunks = new Map(); // Map to store chunks with key "x,y"
let metadata = new Map(); // Map to store metadata for pixels let metadata = new Map(); // Map to store metadata for pixels
let debugMode = false; let debugMode = false;
let fireUpdateCounter = 0; let fireUpdateCounter = 0;
let generatedChunks = new Set(); // Set to track which chunks have been generated
// Initialize the simulation // Initialize the simulation
window.onload = function() { window.onload = function() {
@ -94,8 +95,9 @@ window.onload = function() {
canvas.addEventListener('touchmove', handleTouchMove); canvas.addEventListener('touchmove', handleTouchMove);
canvas.addEventListener('touchend', handleMouseUp); canvas.addEventListener('touchend', handleMouseUp);
// Initialize the first chunk // Initialize the first chunk and generate terrain around it
getOrCreateChunk(0, 0); getOrCreateChunk(0, 0);
generateChunksAroundPlayer();
// Start the simulation loop // Start the simulation loop
requestAnimationFrame(simulationLoop); requestAnimationFrame(simulationLoop);
@ -288,6 +290,9 @@ function moveWorld(dx, dy) {
worldOffsetX += dx; worldOffsetX += dx;
worldOffsetY += dy; worldOffsetY += dy;
updateCoordinatesDisplay(); updateCoordinatesDisplay();
// Generate terrain for chunks around the current view
generateChunksAroundPlayer();
} }
function updateCoordinatesDisplay() { function updateCoordinatesDisplay() {
@ -1040,6 +1045,183 @@ function toggleDebug() {
document.getElementById('debug-btn').classList.toggle('active'); document.getElementById('debug-btn').classList.toggle('active');
} }
// Terrain generation functions
function generateChunksAroundPlayer() {
const centerChunkX = Math.floor(worldOffsetX / CHUNK_SIZE);
const centerChunkY = Math.floor(worldOffsetY / CHUNK_SIZE);
const radius = 3; // Generate chunks within 3 chunks of the player
// Generate chunks in a square around the player
for (let dy = -radius; dy <= radius; dy++) {
for (let dx = -radius; dx <= radius; dx++) {
const chunkX = centerChunkX + dx;
const chunkY = centerChunkY + dy;
// Only generate terrain for chunks at or above y=0
if (chunkY >= 0) {
getOrCreateChunk(chunkX, chunkY);
}
}
}
}
function generateTerrain(chunkX, chunkY, chunkData) {
// Use a seeded random number generator based on chunk coordinates
const seed = chunkX * 10000 + chunkY;
const random = createSeededRandom(seed);
// Generate base terrain (hills)
generateHills(chunkX, chunkY, chunkData, random);
// Add lakes
if (random() < 0.3) { // 30% chance for a lake in a chunk
generateLake(chunkX, chunkY, chunkData, random);
}
// Add stone formations
if (random() < 0.4) { // 40% chance for stone formations
generateStoneFormation(chunkX, chunkY, chunkData, random);
}
// Add vegetation (seeds and trees)
addVegetation(chunkX, chunkY, chunkData, random);
}
function createSeededRandom(seed) {
// Simple seeded random function
return function() {
seed = (seed * 9301 + 49297) % 233280;
return seed / 233280;
};
}
function generateHills(chunkX, chunkY, chunkData, random) {
// Generate a height map for this chunk using simplex-like noise
const heightMap = new Array(CHUNK_SIZE).fill(0);
// Base height (higher for chunks further from origin)
const baseHeight = Math.max(0, 50 - Math.sqrt(chunkX*chunkX + chunkY*chunkY) * 5);
// Generate a smooth height map
for (let x = 0; x < CHUNK_SIZE; x++) {
// Use multiple frequencies for more natural looking terrain
const noise1 = Math.sin(x * 0.02 + chunkX * CHUNK_SIZE * 0.02 + random() * 10) * 15;
const noise2 = Math.sin(x * 0.05 + chunkX * CHUNK_SIZE * 0.05 + random() * 10) * 7;
const noise3 = Math.sin(x * 0.1 + chunkX * CHUNK_SIZE * 0.1 + random() * 10) * 3;
// Combine noise at different frequencies
heightMap[x] = Math.floor(baseHeight + noise1 + noise2 + noise3);
// Ensure height is positive
heightMap[x] = Math.max(0, heightMap[x]);
}
// Fill the terrain based on the height map
for (let x = 0; x < CHUNK_SIZE; x++) {
const height = heightMap[x];
for (let y = CHUNK_SIZE - 1; y >= 0; y--) {
const worldY = chunkY * CHUNK_SIZE + y;
const depth = CHUNK_SIZE - 1 - y;
if (depth < height) {
const index = y * CHUNK_SIZE + x;
// Top layer is grass
if (depth === 0) {
chunkData[index] = GRASS;
}
// Next few layers are dirt
else if (depth < 5) {
chunkData[index] = DIRT;
}
// Deeper layers are stone
else {
chunkData[index] = STONE;
}
}
}
}
}
function generateLake(chunkX, chunkY, chunkData, random) {
// Lake parameters
const lakeX = Math.floor(random() * (CHUNK_SIZE - 60)) + 30;
const lakeY = Math.floor(random() * (CHUNK_SIZE - 60)) + 30;
const lakeWidth = Math.floor(random() * 40) + 20;
const lakeHeight = Math.floor(random() * 20) + 10;
// Create an elliptical lake
for (let y = 0; y < CHUNK_SIZE; y++) {
for (let x = 0; x < CHUNK_SIZE; x++) {
// Calculate distance from lake center (normalized to create an ellipse)
const dx = (x - lakeX) / lakeWidth;
const dy = (y - lakeY) / lakeHeight;
const distance = Math.sqrt(dx*dx + dy*dy);
if (distance < 1) {
const index = y * CHUNK_SIZE + x;
// Water in the center
if (distance < 0.8) {
chunkData[index] = WATER;
}
// Sand around the edges
else {
chunkData[index] = SAND;
}
}
}
}
}
function generateStoneFormation(chunkX, chunkY, chunkData, random) {
// Stone formation parameters
const formationX = Math.floor(random() * (CHUNK_SIZE - 40)) + 20;
const formationWidth = Math.floor(random() * 30) + 10;
const formationHeight = Math.floor(random() * 40) + 20;
// Create a stone hill/mountain
for (let x = formationX - formationWidth; x < formationX + formationWidth; x++) {
if (x < 0 || x >= CHUNK_SIZE) continue;
// Calculate height at this x position (higher in the middle)
const dx = (x - formationX) / formationWidth;
const height = Math.floor(formationHeight * (1 - dx*dx));
for (let y = CHUNK_SIZE - 1; y >= CHUNK_SIZE - height; y--) {
if (y < 0 || y >= CHUNK_SIZE) continue;
const index = y * CHUNK_SIZE + x;
chunkData[index] = STONE;
}
}
}
function addVegetation(chunkX, chunkY, chunkData, random) {
// Add vegetation on grass
for (let y = 0; y < CHUNK_SIZE; y++) {
for (let x = 0; x < CHUNK_SIZE; x++) {
const index = y * CHUNK_SIZE + x;
// Only add vegetation on grass
if (chunkData[index] === GRASS) {
// Check if there's empty space above
if (y > 0 && chunkData[(y-1) * CHUNK_SIZE + x] === EMPTY) {
// Random chance to add different types of vegetation
const roll = random();
if (roll < 0.01) { // 1% chance for a tree seed
chunkData[(y-1) * CHUNK_SIZE + x] = TREE_SEED;
} else if (roll < 0.05) { // 4% chance for a regular seed
chunkData[(y-1) * CHUNK_SIZE + x] = SEED;
}
}
}
}
}
}
function render() { function render() {
// Clear the canvas // Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(0, 0, canvas.width, canvas.height);