// Rendering functions // Cache for rendered chunks let chunkCanvasCache = new Map(); function render() { // Clear the canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Get visible chunks const visibleChunks = getVisibleChunks(); // Render each visible chunk for (const { chunkX, chunkY, isVisible } of visibleChunks) { // Skip rendering for chunks that are not visible if (!isVisible) continue; const key = getChunkKey(chunkX, chunkY); if (!chunks.has(key)) continue; // Calculate screen position of chunk const screenX = (chunkX * CHUNK_SIZE - worldOffsetX) * PIXEL_SIZE; const screenY = (chunkY * CHUNK_SIZE - worldOffsetY) * PIXEL_SIZE; // Check if we need to render this chunk const needsRender = dirtyChunks.has(key) || worldMoved || !chunkCanvasCache.has(key); // Always render the chunk if it's in the stone layer (for visibility) // or if it needs rendering if (needsRender) { renderChunkToCache(chunkX, chunkY, key); } // Draw the cached chunk to the main canvas const cachedCanvas = chunkCanvasCache.get(key); if (cachedCanvas) { ctx.drawImage(cachedCanvas, screenX, screenY); } // Draw chunk border in debug mode if (debugMode) { ctx.strokeStyle = '#ff0000'; ctx.lineWidth = 1; ctx.strokeRect( screenX, screenY, CHUNK_SIZE * PIXEL_SIZE, CHUNK_SIZE * PIXEL_SIZE ); // Draw chunk coordinates ctx.fillStyle = '#ffffff'; ctx.font = '12px Arial'; ctx.fillText(`${chunkX},${chunkY}`, screenX + 5, screenY + 15); } // Remove this chunk from the dirty list after rendering if (dirtyChunks.has(key)) { dirtyChunks.delete(key); } } // Reset world moved flag after rendering worldMoved = false; // Render physics objects renderPhysicsObjects(ctx, worldOffsetX, worldOffsetY); // Render entities if (typeof renderEntities === 'function') { renderEntities(ctx, worldOffsetX, worldOffsetY); } // Draw cursor position and update debug info if (currentMouseX !== undefined && currentMouseY !== undefined) { const worldX = Math.floor(currentMouseX / PIXEL_SIZE) + worldOffsetX; const worldY = Math.floor(currentMouseY / PIXEL_SIZE) + worldOffsetY; // Update coordinates display in debug mode if (debugMode) { document.getElementById('coords').textContent = `Chunk: ${Math.floor(worldOffsetX / CHUNK_SIZE)},${Math.floor(worldOffsetY / CHUNK_SIZE)} | ` + `Mouse: ${worldX},${worldY} | Offset: ${Math.floor(worldOffsetX)},${Math.floor(worldOffsetY)}`; // Draw cursor outline const cursorScreenX = (worldX - worldOffsetX) * PIXEL_SIZE; const cursorScreenY = (worldY - worldOffsetY) * PIXEL_SIZE; ctx.strokeStyle = '#00ff00'; ctx.lineWidth = 2; ctx.strokeRect( cursorScreenX - PIXEL_SIZE, cursorScreenY - PIXEL_SIZE, PIXEL_SIZE * 3, PIXEL_SIZE * 3 ); // Draw a dot at the exact mouse position ctx.fillStyle = '#ff0000'; ctx.beginPath(); ctx.arc(currentMouseX, currentMouseY, 3, 0, Math.PI * 2); ctx.fill(); } } } // Render a chunk to an offscreen canvas and cache it function renderChunkToCache(chunkX, chunkY, key) { const chunk = chunks.get(key); // Create a new canvas for this chunk if it doesn't exist if (!chunkCanvasCache.has(key)) { const chunkCanvas = document.createElement('canvas'); chunkCanvas.width = CHUNK_SIZE * PIXEL_SIZE; chunkCanvas.height = CHUNK_SIZE * PIXEL_SIZE; chunkCanvasCache.set(key, chunkCanvas); } const chunkCanvas = chunkCanvasCache.get(key); const chunkCtx = chunkCanvas.getContext('2d'); // Clear the chunk canvas chunkCtx.clearRect(0, 0, chunkCanvas.width, chunkCanvas.height); // Render each pixel in the chunk for (let y = 0; y < CHUNK_SIZE; y++) { for (let x = 0; x < CHUNK_SIZE; x++) { const index = y * CHUNK_SIZE + x; const type = chunk[index]; // Always render stone layer even if it's not directly visible if (type === EMPTY && chunkY !== 1) continue; // For the stone layer (chunkY = 1), render a faint background even for empty spaces if (type === EMPTY && chunkY === 1) { // Use a very faint gray for empty spaces in the stone layer chunkCtx.fillStyle = 'rgba(100, 100, 100, 0.2)'; chunkCtx.fillRect( x * PIXEL_SIZE, y * PIXEL_SIZE, PIXEL_SIZE, PIXEL_SIZE ); continue; } // Set color based on type if (type === SAND) { chunkCtx.fillStyle = SAND_COLOR; } else if (type === WATER) { // Get water color from metadata with variation const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y); const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0; chunkCtx.fillStyle = WATER_COLORS[colorIndex]; } else if (type === WALL) { chunkCtx.fillStyle = WALL_COLOR; } else if (type === DIRT) { // Get dirt color from metadata with variation const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y); const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0; chunkCtx.fillStyle = DIRT_COLORS[colorIndex]; } else if (type === STONE) { // Get stone color from metadata with variation const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y); const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0; chunkCtx.fillStyle = STONE_COLORS[colorIndex]; } else if (type === GRASS) { // Get grass color from metadata with variation const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y); const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0; chunkCtx.fillStyle = GRASS_COLORS[colorIndex]; } else if (type === WOOD) { // Get wood color from metadata with variation const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y); const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0; chunkCtx.fillStyle = WOOD_COLORS[colorIndex]; } else if (type === SEED) { chunkCtx.fillStyle = SEED_COLOR; } else if (type === GRASS_BLADE) { // Use the same color variation as grass const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y); const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0; chunkCtx.fillStyle = GRASS_COLORS[colorIndex]; } else if (type === FLOWER) { // Get flower color from metadata or use a default const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y); chunkCtx.fillStyle = metadata && metadata.color ? metadata.color : FLOWER_COLORS[0]; } else if (type === TREE_SEED) { chunkCtx.fillStyle = SEED_COLOR; } else if (type === LEAF) { // Get leaf color from metadata with variation const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y); const colorIndex = metadata && metadata.colorIndex !== undefined ? metadata.colorIndex : 0; chunkCtx.fillStyle = LEAF_COLORS[colorIndex]; } else if (type === FIRE) { // Get fire color from metadata const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y); const colorIndex = metadata ? metadata.colorIndex : 0; chunkCtx.fillStyle = FIRE_COLORS[colorIndex]; } else if (type === LAVA) { // Get lava color from metadata const metadata = getMetadata(chunkX * CHUNK_SIZE + x, chunkY * CHUNK_SIZE + y); const colorIndex = metadata ? metadata.colorIndex : 0; chunkCtx.fillStyle = LAVA_COLORS[colorIndex]; } // Draw the pixel chunkCtx.fillRect( x * PIXEL_SIZE, y * PIXEL_SIZE, PIXEL_SIZE, PIXEL_SIZE ); } } }