import * as THREE from "three"; /** * VoxelManager.js * Handles the Three.js rendering of the VoxelGrid data. * Uses InstancedMesh for high performance. */ export class VoxelManager { constructor(grid, scene, textureAtlas) { this.grid = grid; this.scene = scene; this.textureAtlas = textureAtlas; this.mesh = null; this.needsUpdate = true; // Define Materials per ID (Simplified for Prototype) // In Phase 3, this will use the Texture Atlas UVs this.material = new THREE.MeshStandardMaterial({ color: 0xffffff }); // Color Map: ID -> Hex this.palette = { 1: new THREE.Color(0x555555), // Stone 2: new THREE.Color(0x3d2817), // Dirt 10: new THREE.Color(0x8b4513), // Wood (Destructible) 15: new THREE.Color(0x00ffff), // Crystal }; } /** * Initializes the InstancedMesh based on grid size. * Must be called after the grid is populated by WorldGen. */ init() { if (this.mesh) { this.scene.remove(this.mesh); this.mesh.dispose(); } const geometry = new THREE.BoxGeometry(1, 1, 1); const count = this.grid.size.x * this.grid.size.y * this.grid.size.z; this.mesh = new THREE.InstancedMesh(geometry, this.material, count); this.mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage); // Allow updates this.scene.add(this.mesh); this.update(); } /** * Re-calculates positions for all voxels. * Call this when terrain is destroyed or modified. */ update() { if (!this.mesh) return; let instanceId = 0; const dummy = new THREE.Object3D(); for (let y = 0; y < this.grid.size.y; y++) { for (let z = 0; z < this.grid.size.z; z++) { for (let x = 0; x < this.grid.size.x; x++) { const cellId = this.grid.getCell(x, y, z); if (cellId !== 0) { // Position the cube dummy.position.set(x, y, z); dummy.updateMatrix(); // Apply Transform this.mesh.setMatrixAt(instanceId, dummy.matrix); // Apply Color based on ID const color = this.palette[cellId] || new THREE.Color(0xff00ff); // Magenta = Error this.mesh.setColorAt(instanceId, color); } else { // Hide Air voxels by scaling to 0 dummy.position.set(0, 0, 0); dummy.scale.set(0, 0, 0); dummy.updateMatrix(); this.mesh.setMatrixAt(instanceId, dummy.matrix); // Reset scale for next iteration dummy.scale.set(1, 1, 1); } instanceId++; } } } this.mesh.instanceMatrix.needsUpdate = true; if (this.mesh.instanceColor) this.mesh.instanceColor.needsUpdate = true; } /** * Efficiently updates a single voxel without rebuilding the whole mesh. * Use this for 'destroyVoxel' events. */ updateVoxel(x, y, z) { // Calculate the specific index in the flat array const index = y * this.grid.size.x * this.grid.size.z + z * this.grid.size.x + x; const cellId = this.grid.getCell(x, y, z); const dummy = new THREE.Object3D(); if (cellId !== 0) { dummy.position.set(x, y, z); dummy.updateMatrix(); this.mesh.setMatrixAt(index, dummy.matrix); const color = this.palette[cellId] || new THREE.Color(0xff00ff); this.mesh.setColorAt(index, color); } else { // Hide it dummy.scale.set(0, 0, 0); dummy.updateMatrix(); this.mesh.setMatrixAt(index, dummy.matrix); } this.mesh.instanceMatrix.needsUpdate = true; this.mesh.instanceColor.needsUpdate = true; } }