2025-12-16 23:52:58 +00:00
|
|
|
import * as THREE from "three";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* VoxelManager.js
|
2025-12-17 19:26:42 +00:00
|
|
|
* Handles the Three.js rendering of the VoxelGrid data.
|
|
|
|
|
* Uses InstancedMesh for high performance.
|
2025-12-16 23:52:58 +00:00
|
|
|
*/
|
|
|
|
|
export class VoxelManager {
|
2025-12-17 19:26:42 +00:00
|
|
|
constructor(grid, scene, textureAtlas) {
|
2025-12-16 23:52:58 +00:00
|
|
|
this.grid = grid;
|
|
|
|
|
this.scene = scene;
|
2025-12-17 19:26:42 +00:00
|
|
|
this.textureAtlas = textureAtlas;
|
2025-12-16 23:52:58 +00:00
|
|
|
this.mesh = null;
|
2025-12-17 19:26:42 +00:00
|
|
|
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
|
|
|
|
|
};
|
2025-12-16 23:52:58 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-17 19:26:42 +00:00
|
|
|
/**
|
|
|
|
|
* Initializes the InstancedMesh based on grid size.
|
|
|
|
|
* Must be called after the grid is populated by WorldGen.
|
|
|
|
|
*/
|
2025-12-16 23:52:58 +00:00
|
|
|
init() {
|
2025-12-17 19:26:42 +00:00
|
|
|
if (this.mesh) {
|
|
|
|
|
this.scene.remove(this.mesh);
|
|
|
|
|
this.mesh.dispose();
|
|
|
|
|
}
|
2025-12-16 23:52:58 +00:00
|
|
|
|
2025-12-17 19:26:42 +00:00
|
|
|
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
|
|
|
|
const count = this.grid.size.x * this.grid.size.y * this.grid.size.z;
|
2025-12-16 23:52:58 +00:00
|
|
|
|
2025-12-17 19:26:42 +00:00
|
|
|
this.mesh = new THREE.InstancedMesh(geometry, this.material, count);
|
|
|
|
|
this.mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage); // Allow updates
|
2025-12-16 23:52:58 +00:00
|
|
|
|
|
|
|
|
this.scene.add(this.mesh);
|
2025-12-17 19:26:42 +00:00
|
|
|
this.update();
|
2025-12-16 23:52:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-12-17 19:26:42 +00:00
|
|
|
* Re-calculates positions for all voxels.
|
|
|
|
|
* Call this when terrain is destroyed or modified.
|
2025-12-16 23:52:58 +00:00
|
|
|
*/
|
|
|
|
|
update() {
|
2025-12-17 19:26:42 +00:00
|
|
|
if (!this.mesh) return;
|
2025-12-16 23:52:58 +00:00
|
|
|
|
|
|
|
|
let instanceId = 0;
|
|
|
|
|
const dummy = new THREE.Object3D();
|
|
|
|
|
|
2025-12-17 19:26:42 +00:00
|
|
|
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);
|
2025-12-16 23:52:58 +00:00
|
|
|
|
2025-12-17 19:26:42 +00:00
|
|
|
if (cellId !== 0) {
|
|
|
|
|
// Position the cube
|
2025-12-16 23:52:58 +00:00
|
|
|
dummy.position.set(x, y, z);
|
|
|
|
|
dummy.updateMatrix();
|
|
|
|
|
|
2025-12-17 19:26:42 +00:00
|
|
|
// Apply Transform
|
2025-12-16 23:52:58 +00:00
|
|
|
this.mesh.setMatrixAt(instanceId, dummy.matrix);
|
|
|
|
|
|
2025-12-17 19:26:42 +00:00
|
|
|
// Apply Color based on ID
|
|
|
|
|
const color = this.palette[cellId] || new THREE.Color(0xff00ff); // Magenta = Error
|
2025-12-16 23:52:58 +00:00
|
|
|
this.mesh.setColorAt(instanceId, color);
|
2025-12-17 19:26:42 +00:00
|
|
|
} 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);
|
2025-12-16 23:52:58 +00:00
|
|
|
}
|
2025-12-17 19:26:42 +00:00
|
|
|
|
|
|
|
|
instanceId++;
|
2025-12-16 23:52:58 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.mesh.instanceMatrix.needsUpdate = true;
|
2025-12-17 19:26:42 +00:00
|
|
|
if (this.mesh.instanceColor) this.mesh.instanceColor.needsUpdate = true;
|
|
|
|
|
}
|
2025-12-16 23:52:58 +00:00
|
|
|
|
2025-12-17 19:26:42 +00:00
|
|
|
/**
|
|
|
|
|
* 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);
|
2025-12-16 23:52:58 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-17 19:26:42 +00:00
|
|
|
this.mesh.instanceMatrix.needsUpdate = true;
|
|
|
|
|
this.mesh.instanceColor.needsUpdate = true;
|
2025-12-16 23:52:58 +00:00
|
|
|
}
|
|
|
|
|
}
|