sandsim/js/terrain.js

189 lines
7.3 KiB
JavaScript

// 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;
// Set metadata with color index for grass
const worldX = chunkX * CHUNK_SIZE + x;
const worldY = chunkY * CHUNK_SIZE + y;
setMetadata(worldX, worldY, { colorIndex: Math.floor(Math.random() * 10) });
}
// Next few layers are dirt
else if (depth < 5) {
chunkData[index] = DIRT;
// Set metadata with color index for dirt
const worldX = chunkX * CHUNK_SIZE + x;
const worldY = chunkY * CHUNK_SIZE + y;
setMetadata(worldX, worldY, { colorIndex: Math.floor(Math.random() * 10) });
}
// Deeper layers are stone
else {
chunkData[index] = STONE;
// Set metadata with color index for stone
const worldX = chunkX * CHUNK_SIZE + x;
const worldY = chunkY * CHUNK_SIZE + y;
setMetadata(worldX, worldY, { colorIndex: Math.floor(Math.random() * 10) });
}
}
}
}
}
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;
}
}
}
}
}
}