This is the terrain:
And my objective is to optimize it. Its possible? Thanks anyway!
Where is your source image from?
There are a few well known techniques for doing large voxel fields.
greedy meshing..
sparse voxel octtrees..
raymarched texture encoded distance fields..
They are all pretty complex to implement.
You’ll also need to use texture atlasses or texture arrays to get textured cubes.
marched volumes:
greedy meshing:
A case against greedy meshing:
SVO:
It’s a HUUUUUGE topic.
I did this for the moment:
<script>
onload=()=>{
k=[]
xa=0
ya=0
onkeydown=onkeyup=(e)=>{k[e.keyCode]=e.type=="keydown"}
document.body.requestPointerLock=document.body.requestPointerLock||document.body.mozRequestPointerLock
document.title="Voxel Sphere."
document.body.style.margin=0
scene=new THREE.Scene()
camera=new THREE.PerspectiveCamera(75,innerWidth/innerHeight,0.1,10000)
renderer=new THREE.WebGLRenderer()
document.body.appendChild(renderer.domElement)
// Dimensiones
const size = 100;
const blockSize = 1;
const blocks = new Set();
// Simula todos los bloques llenos
for (let x = 0; x < size; x++) {
for (let y = 0; y < size; y++) {
for (let z = 0; z < size; z++) {
if(Math.random()<0.9){
blocks.add(`${x},${y},${z}`);
}
}
}
}
// Caras unitarias (de 1x1) en cada eje positivo
const faceOffsets = [
{ dir: [1, 0, 0], verts: [[0.5, -0.5, -0.5], [0.5, 0.5, -0.5], [0.5, 0.5, 0.5], [0.5, -0.5, 0.5]] }, // Right
{ dir: [-1, 0, 0], verts: [[-0.5, -0.5, 0.5], [-0.5, 0.5, 0.5], [-0.5, 0.5, -0.5], [-0.5, -0.5, -0.5]] }, // Left
{ dir: [0, 1, 0], verts: [[-0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [0.5, 0.5, -0.5], [-0.5, 0.5, -0.5]] }, // Top
{ dir: [0, -1, 0], verts: [[-0.5, -0.5, -0.5], [0.5, -0.5, -0.5], [0.5, -0.5, 0.5], [-0.5, -0.5, 0.5]] }, // Bottom
{ dir: [0, 0, 1], verts: [[-0.5, -0.5, 0.5], [0.5, -0.5, 0.5], [0.5, 0.5, 0.5], [-0.5, 0.5, 0.5]] }, // Front
{ dir: [0, 0, -1], verts: [[0.5, -0.5, -0.5], [-0.5, -0.5, -0.5], [-0.5, 0.5, -0.5], [0.5, 0.5, -0.5]] } // Back
];
const positions = [];
const indices = [];
let index = 0;
for (let x = 0; x < size; x++) {
for (let y = 0; y < size; y++) {
for (let z = 0; z < size; z++) {
const pos = `${x},${y},${z}`;
if (!blocks.has(pos)) continue;
for (const { dir, verts } of faceOffsets) {
const [dx, dy, dz] = dir;
const neighbor = `${x + dx},${y + dy},${z + dz}`;
if (!blocks.has(neighbor)) {
// Esta cara es visible, agregarla
const face = verts.map(([vx, vy, vz]) => [
vx + x,
vy + y,
vz + z
]);
const baseIndex = index;
face.forEach(v => positions.push(...v));
indices.push(
baseIndex, baseIndex + 1, baseIndex + 2,
baseIndex, baseIndex + 2, baseIndex + 3
);
index += 4;
}
}
}
}
}
// Crear geometrĂa y malla
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geometry.setIndex(indices);
geometry.computeVertexNormals();
const material = new THREE.MeshStandardMaterial({ map : new THREE.TextureLoader().load("dirt.jpg") });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
scene.background=new THREE.Color("rgb(0,127,255)")
scene.fog=new THREE.Fog("rgb(0,127,255)",100,500)
light=new THREE.PointLight("rgb(255,255,255)",1,500)
scene.add(light)
onmousedown=()=>{
if(document.pointerLockElement===document.body||
document.mozPointerLockElement===document.body){
}else{
document.body.requestPointerLock()
}
}
onmousemove=(event)=>{
if(document.pointerLockElement===document.body||
document.mozPointerLockElement===document.body){
xa-=0.01*event.movementX
if(-1<ya&&0<event.movementY){
ya-=0.01*event.movementY
}
if(ya<1&&event.movementY<0){
ya-=0.01*event.movementY
}
}
}
render=()=>{
renderer.setSize(innerWidth,innerHeight)
camera.aspect=innerWidth/innerHeight
camera.updateProjectionMatrix()
requestAnimationFrame(render)
renderer.render(scene,camera)
}
render()
setInterval(()=>{
light.position.x=camera.position.x
light.position.y=camera.position.y
light.position.z=camera.position.z
camera.lookAt(
camera.position.x+Math.sin(xa)*Math.cos(ya),
camera.position.y+Math.sin(ya),
camera.position.z+Math.cos(xa)*Math.cos(ya)
)
if(k[65]){
camera.position.x+=0.1*Math.cos(xa)
camera.position.z-=0.1*Math.sin(xa)
}
if(k[87]){
camera.position.x+=0.1*Math.sin(xa)
camera.position.z+=0.1*Math.cos(xa)
}
if(k[68]){
camera.position.x-=0.1*Math.cos(xa)
camera.position.z+=0.1*Math.sin(xa)
}
if(k[83]){
camera.position.x-=0.1*Math.sin(xa)
camera.position.z-=0.1*Math.cos(xa)
}
if(k[32]){
camera.position.y+=0.1
}
if(k[88]){
camera.position.y-=0.1
}
},1)
}
</script>
Im wanting to implement a texture image called “dirt.jpg” to my file, but didnt know how…
How are you running your code.. using vite? Or are you double clicking your html file?
Im now using localhost with dirt texture. I want infinite generation:
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/simplex-noise@2.4.0/simplex-noise.min.js"></script>
<script>
onload = () => {
const k = [];
let xa = 0, ya = 0;
onkeydown = onkeyup = (e) => { k[e.keyCode] = e.type == "keydown"; };
document.body.requestPointerLock = document.body.requestPointerLock || document.body.mozRequestPointerLock;
document.title = "MadDrFrank's Voxels in JS.";
document.body.style.margin = 0;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 10000);
const renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);
const size = 30;
const blockSize = 1;
let blocks = new Set();
let mesh = null; // Variable global para el mesh
const simplex = new SimplexNoise();
const seed = Math.floor(Math.random() * 10 ** 5);
camera.position.set(0,0,0)
function eliminarBloques(scene) {
if (mesh) {
scene.remove(mesh); // Quita el mesh de la escena
mesh.geometry.dispose(); // Libera la geometrĂa
mesh.material.dispose(); // Libera el material
mesh = null; // Limpia la referencia
}
blocks = new Set(); // VacĂa el conjunto de bloques
}
generateDirt=()=>{
try{
eliminarBloques(scene)
}catch(e){}
for (let x = camera.position.x-size; x < camera.position.x+size; x++) {
for (let y = camera.position.y-size; y < camera.position.y+size; y++) {
for (let z = camera.position.z-size; z < camera.position.z+size; z++) {
if (
simplex.noise3D(x/25,y/25,z/25+seed)<0.5
&&
y<simplex.noise2D(x/25,z/25+seed)*5+simplex.noise2D(x/125,z/125)*25
) {
blocks.add(`${x},${y},${z}`);
}
}
}
}
const faceOffsets = [
{ dir: [1, 0, 0], verts: [[0.5, -0.5, -0.5], [0.5, 0.5, -0.5], [0.5, 0.5, 0.5], [0.5, -0.5, 0.5]] },
{ dir: [-1, 0, 0], verts: [[-0.5, -0.5, 0.5], [-0.5, 0.5, 0.5], [-0.5, 0.5, -0.5], [-0.5, -0.5, -0.5]] },
{ dir: [0, 1, 0], verts: [[-0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [0.5, 0.5, -0.5], [-0.5, 0.5, -0.5]] },
{ dir: [0, -1, 0], verts: [[-0.5, -0.5, -0.5], [0.5, -0.5, -0.5], [0.5, -0.5, 0.5], [-0.5, -0.5, 0.5]] },
{ dir: [0, 0, 1], verts: [[-0.5, -0.5, 0.5], [0.5, -0.5, 0.5], [0.5, 0.5, 0.5], [-0.5, 0.5, 0.5]] },
{ dir: [0, 0, -1], verts: [[0.5, -0.5, -0.5], [-0.5, -0.5, -0.5], [-0.5, 0.5, -0.5], [0.5, 0.5, -0.5]] }
];
const positions = [];
const indices = [];
const uvs = [];
let index = 0;
for (let x = camera.position.x-size; x < camera.position.x+size; x++) {
for (let y = camera.position.y-size; y < camera.position.y+size; y++) {
for (let z = camera.position.z-size; z < camera.position.z+size; z++) {
const pos = `${x},${y},${z}`;
if (!blocks.has(pos)) continue;
for (const { dir, verts } of faceOffsets) {
const [dx, dy, dz] = dir;
const neighbor = `${x + dx},${y + dy},${z + dz}`;
if (!blocks.has(neighbor)) {
const face = verts.map(([vx, vy, vz]) => [vx + x, vy + y, vz + z]);
const baseIndex = index;
face.forEach(v => positions.push(...v));
// UVs para que la textura se repita por cara
uvs.push(0, 0);
uvs.push(1, 0);
uvs.push(1, 1);
uvs.push(0, 1);
indices.push(
baseIndex, baseIndex + 1, baseIndex + 2,
baseIndex, baseIndex + 2, baseIndex + 3
);
index += 4;
}
}
}
}
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
geometry.setIndex(indices);
geometry.computeVertexNormals();
const loader = new THREE.TextureLoader();
const texture = loader.load("dirt.jpg");
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.magFilter = THREE.NearestFilter;
const material = new THREE.MeshStandardMaterial({ map: texture });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
}
generateDirt()
setInterval(generateDirt,5000)
scene.background = new THREE.Color("rgb(0,127,255)");
scene.fog = new THREE.Fog("rgb(0,127,255)", 100, 500);
const light = new THREE.PointLight("rgb(255,255,255)", 1, 500);
scene.add(light);
onmousedown = () => {
if (!(document.pointerLockElement === document.body || document.mozPointerLockElement === document.body)) {
document.body.requestPointerLock();
}
};
onmousemove = (event) => {
if (document.pointerLockElement === document.body || document.mozPointerLockElement === document.body) {
xa -= 0.01 * event.movementX;
ya = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, ya - 0.01 * event.movementY));
}
};
const render = () => {
renderer.setSize(innerWidth, innerHeight);
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
requestAnimationFrame(render);
renderer.render(scene, camera);
};
const walkSpeed = 5;
render();
setInterval(() => {
light.position.copy(camera.position);
if(ya<-1.5)ya=-1.5
if(ya>1.5)ya=1.5
camera.lookAt(
camera.position.x + Math.sin(xa) * Math.cos(ya),
camera.position.y + Math.sin(ya),
camera.position.z + Math.cos(xa) * Math.cos(ya)
);
if (k[65]) {
camera.position.x += 0.1 * Math.cos(xa) * walkSpeed;
camera.position.z -= 0.1 * Math.sin(xa) * walkSpeed;
}
if (k[87]) {
camera.position.x += 0.1 * Math.sin(xa) * walkSpeed;
camera.position.z += 0.1 * Math.cos(xa) * walkSpeed;
}
if (k[68]) {
camera.position.x -= 0.1 * Math.cos(xa) * walkSpeed;
camera.position.z += 0.1 * Math.sin(xa) * walkSpeed;
}
if (k[83]) {
camera.position.x -= 0.1 * Math.sin(xa) * walkSpeed;
camera.position.z -= 0.1 * Math.cos(xa) * walkSpeed;
}
if (k[69]) camera.position.y += 0.1 * walkSpeed;
if (k[81]) camera.position.y -= 0.1 * walkSpeed;
}, 1);
};
</script>
EDIT 1: And performance if its possible…
EDIT 2: I did this another example:
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/simplex-noise@2.4.0/simplex-noise.min.js"></script>
<script>
onload = () => {
const k = [];
let xa = 0, ya = 0;
onkeydown = onkeyup = (e) => { k[e.keyCode] = e.type == "keydown"; };
document.body.requestPointerLock = document.body.requestPointerLock || document.body.mozRequestPointerLock;
document.title = "MadDrFrank's Voxels in JS.";
document.body.style.margin = 0;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 10000);
const renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);
const size = 20;
const blockSize = 1;
let blocks = new Set();
mesh = null; // Variable global para el mesh
const simplex = new SimplexNoise();
const seed = Math.floor(Math.random() * 10 ** 5);
camera.position.set(0,0,0)
torque={x:0,y:0,z:0}
generateDirt=()=>{
try{
if(mesh){
scene.remove(mesh)
}
}catch(e){
console.warn(e.message)
}
for (let x = camera.position.x-size; x < camera.position.x+size; x++) {
for (let y = camera.position.y-size; y < camera.position.y+size; y++) {
for (let z = camera.position.z-size; z < camera.position.z+size; z++) {
if (
simplex.noise3D(x/25,y/25,z/25+seed)<0.5
&&
y<simplex.noise2D(x/25,z/25+seed)*5+simplex.noise2D(x/125,z/125)*25
) {
blocks.add(`${x},${y},${z}`);
}
}
}
}
const faceOffsets = [
{ dir: [1, 0, 0], verts: [[0.5, -0.5, -0.5], [0.5, 0.5, -0.5], [0.5, 0.5, 0.5], [0.5, -0.5, 0.5]] },
{ dir: [-1, 0, 0], verts: [[-0.5, -0.5, 0.5], [-0.5, 0.5, 0.5], [-0.5, 0.5, -0.5], [-0.5, -0.5, -0.5]] },
{ dir: [0, 1, 0], verts: [[-0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [0.5, 0.5, -0.5], [-0.5, 0.5, -0.5]] },
{ dir: [0, -1, 0], verts: [[-0.5, -0.5, -0.5], [0.5, -0.5, -0.5], [0.5, -0.5, 0.5], [-0.5, -0.5, 0.5]] },
{ dir: [0, 0, 1], verts: [[-0.5, -0.5, 0.5], [0.5, -0.5, 0.5], [0.5, 0.5, 0.5], [-0.5, 0.5, 0.5]] },
{ dir: [0, 0, -1], verts: [[0.5, -0.5, -0.5], [-0.5, -0.5, -0.5], [-0.5, 0.5, -0.5], [0.5, 0.5, -0.5]] }
];
const positions = [];
const indices = [];
const uvs = [];
let index = 0;
for (let x = camera.position.x-size; x < camera.position.x+size; x++) {
for (let y = camera.position.y-size; y < camera.position.y+size; y++) {
for (let z = camera.position.z-size; z < camera.position.z+size; z++) {
const pos = `${x},${y},${z}`;
if (!blocks.has(pos)) continue;
for (const { dir, verts } of faceOffsets) {
const [dx, dy, dz] = dir;
const neighbor = `${x + dx},${y + dy},${z + dz}`;
if (!blocks.has(neighbor)) {
const face = verts.map(([vx, vy, vz]) => [vx + x, vy + y, vz + z]);
const baseIndex = index;
face.forEach(v => positions.push(...v));
// UVs para que la textura se repita por cara
uvs.push(0, 0);
uvs.push(1, 0);
uvs.push(1, 1);
uvs.push(0, 1);
indices.push(
baseIndex, baseIndex + 1, baseIndex + 2,
baseIndex, baseIndex + 2, baseIndex + 3
);
index += 4;
}
}
}
}
}
geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
geometry.setIndex(indices);
geometry.computeVertexNormals();
loader = new THREE.TextureLoader();
texture = loader.load("dirt.jpg");
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.magFilter = THREE.NearestFilter;
material = new THREE.MeshStandardMaterial({ map: texture });
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
}
generateDirt()
scene.background = new THREE.Color("rgb(0,127,255)");
scene.fog = new THREE.Fog("rgb(0,127,255)", 100, 500);
const light = new THREE.PointLight("rgb(255,255,255)", 1, 500);
scene.add(light);
onmousedown = () => {
if (!(document.pointerLockElement === document.body || document.mozPointerLockElement === document.body)) {
document.body.requestPointerLock();
}
};
onmousemove = (event) => {
if (document.pointerLockElement === document.body || document.mozPointerLockElement === document.body) {
xa -= 0.01 * event.movementX;
ya = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, ya - 0.01 * event.movementY));
}
};
const render = () => {
renderer.setSize(innerWidth, innerHeight);
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
requestAnimationFrame(render);
renderer.render(scene, camera);
};
const walkSpeed = 5;
render();
distance=(obj1,obj2)=>{
return ((obj1.x-obj2.x)**2+(obj1.y-obj2.y)**2+(obj1.z-obj2.z)**2)**0.5
}
setInterval(() => {
if(distance(torque,camera.position)>5){
generateDirt()
torque.x=camera.position.x
torque.y=camera.position.y
torque.z=camera.position.z
}
light.position.copy(camera.position)
if(ya<-1.5)ya=-1.5
if(ya>1.5)ya=1.5
camera.lookAt(
camera.position.x + Math.sin(xa) * Math.cos(ya),
camera.position.y + Math.sin(ya),
camera.position.z + Math.cos(xa) * Math.cos(ya)
);
if (k[65]) {
camera.position.x += 0.1 * Math.cos(xa) * walkSpeed;
camera.position.z -= 0.1 * Math.sin(xa) * walkSpeed;
}
if (k[87]) {
camera.position.x += 0.1 * Math.sin(xa) * walkSpeed;
camera.position.z += 0.1 * Math.cos(xa) * walkSpeed;
}
if (k[68]) {
camera.position.x -= 0.1 * Math.cos(xa) * walkSpeed;
camera.position.z += 0.1 * Math.sin(xa) * walkSpeed;
}
if (k[83]) {
camera.position.x -= 0.1 * Math.sin(xa) * walkSpeed;
camera.position.z -= 0.1 * Math.cos(xa) * walkSpeed;
}
if (k[69]) camera.position.y += 0.1 * walkSpeed;
if (k[81]) camera.position.y -= 0.1 * walkSpeed;
}, 1);
};
</script>
EDIT 3:
I getted a little optimized procedural generation, but with some inner walls that i want to remove. Its possible? Here is the code:
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/simplex-noise@2.4.0/simplex-noise.min.js"></script>
<script>
onload = () => {
// --- ConfiguraciĂłn ---
const chunkSize = 64; // tamaño del chunk en bloques (más pequeño = menos lag)
const viewDistanceChunks = 1; // chunks alrededor del jugador
const simplex = new SimplexNoise();
const seed = Math.floor(Math.random() * 10 ** 5);
// --- Setup Three.js ---
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);
document.body.style.margin = "0";
scene.background = new THREE.Color("rgb(0,127,255)");
scene.fog = new THREE.Fog("rgb(0,127,255)", 100, 500);
const light = new THREE.PointLight("rgb(255,255,255)", 1, 500);
scene.add(light);
camera.position.set(0, 20, 0);
let xa = 0, ya = 0;
const k = [];
onkeydown = onkeyup = (e) => { k[e.keyCode] = e.type == "keydown"; };
document.body.requestPointerLock = document.body.requestPointerLock || document.body.mozRequestPointerLock;
onmousedown = () => {
if (!(document.pointerLockElement === document.body || document.mozPointerLockElement === document.body)) {
document.body.requestPointerLock();
}
};
onmousemove = (e) => {
if (document.pointerLockElement === document.body || document.mozPointerLockElement === document.body) {
xa -= 0.002 * e.movementX;
ya = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, ya - 0.002 * e.movementY));
}
};
// --- Variables de chunks ---
const chunkCache = new Map(); // Guarda bloques de cada chunk
const chunkMeshes = new Map(); // Guarda meshes de cada chunk
// --- Helpers ---
function getChunkKey(x, y, z) {
return `${x},${y},${z}`;
}
// Genera datos de bloques para un chunk dado
function generateChunkData(cx, cy, cz) {
const blocks = new Set();
for (let x = 0; x < chunkSize; x++) {
for (let y = 0; y < chunkSize; y++) {
for (let z = 0; z < chunkSize; z++) {
const worldX = cx * chunkSize + x;
const worldY = cy * chunkSize + y;
const worldZ = cz * chunkSize + z;
if (
simplex.noise3D(worldX / 25, worldY / 25, worldZ / 25 + seed) < 0.5 &&
worldY < simplex.noise2D(worldX / 25, worldZ / 25 + seed) * 5 + simplex.noise2D(worldX / 125, worldZ / 125) * 25
) {
blocks.add(`${worldX},${worldY},${worldZ}`);
}
}
}
}
return blocks;
}
// Crea mesh de un chunk a partir de sus bloques
function generateChunkMeshFromData(blocks) {
const faceOffsets = [
{ dir: [1, 0, 0], verts: [[0.5, -0.5, -0.5], [0.5, 0.5, -0.5], [0.5, 0.5, 0.5], [0.5, -0.5, 0.5]] },
{ dir: [-1, 0, 0], verts: [[-0.5, -0.5, 0.5], [-0.5, 0.5, 0.5], [-0.5, 0.5, -0.5], [-0.5, -0.5, -0.5]] },
{ dir: [0, 1, 0], verts: [[-0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [0.5, 0.5, -0.5], [-0.5, 0.5, -0.5]] },
{ dir: [0, -1, 0], verts: [[-0.5, -0.5, -0.5], [0.5, -0.5, -0.5], [0.5, -0.5, 0.5], [-0.5, -0.5, 0.5]] },
{ dir: [0, 0, 1], verts: [[-0.5, -0.5, 0.5], [0.5, -0.5, 0.5], [0.5, 0.5, 0.5], [-0.5, 0.5, 0.5]] },
{ dir: [0, 0, -1], verts: [[0.5, -0.5, -0.5], [-0.5, -0.5, -0.5], [-0.5, 0.5, -0.5], [0.5, 0.5, -0.5]] }
];
const positions = [];
const indices = [];
const uvs = [];
let index = 0;
// Para cada bloque, comprobar vecinos para crear caras visibles
for (const posStr of blocks) {
const [x, y, z] = posStr.split(",").map(Number);
for (const { dir, verts } of faceOffsets) {
const [dx, dy, dz] = dir;
const neighbor = `${x + dx},${y + dy},${z + dz}`;
if (!blocks.has(neighbor)) {
const face = verts.map(([vx, vy, vz]) => [vx + x, vy + y, vz + z]);
const baseIndex = index;
face.forEach(v => positions.push(...v));
uvs.push(0, 0, 1, 0, 1, 1, 0, 1);
indices.push(baseIndex, baseIndex + 1, baseIndex + 2, baseIndex, baseIndex + 2, baseIndex + 3);
index += 4;
}
}
}
if (positions.length === 0) return null;
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
geometry.setIndex(indices);
geometry.computeVertexNormals();
const loader = new THREE.TextureLoader();
const texture = loader.load("dirt.jpg"); // Usa una textura dirt alternativa si quieres
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.magFilter = THREE.NearestFilter;
const material = new THREE.MeshStandardMaterial({ map: texture });
return new THREE.Mesh(geometry, material);
}
// Actualiza chunks visibles según posición de cámara
function updateChunks() {
const cx = Math.floor(camera.position.x / chunkSize);
const cy = Math.floor(camera.position.y / chunkSize);
const cz = Math.floor(camera.position.z / chunkSize);
const neededChunks = new Set();
for (let x = cx - viewDistanceChunks; x <= cx + viewDistanceChunks; x++) {
for (let y = cy - viewDistanceChunks; y <= cy + viewDistanceChunks; y++) {
for (let z = cz - viewDistanceChunks; z <= cz + viewDistanceChunks; z++) {
const key = getChunkKey(x, y, z);
neededChunks.add(key);
if (!chunkMeshes.has(key)) {
if (!chunkCache.has(key)) {
chunkCache.set(key, generateChunkData(x, y, z));
}
const blocks = chunkCache.get(key);
const mesh = generateChunkMeshFromData(blocks);
if (mesh) {
scene.add(mesh);
chunkMeshes.set(key, mesh);
}
}
}
}
}
// Remover chunks fuera del rango
for (const key of chunkMeshes.keys()) {
if (!neededChunks.has(key)) {
const mesh = chunkMeshes.get(key);
scene.remove(mesh);
mesh.geometry.dispose();
mesh.material.dispose();
chunkMeshes.delete(key);
chunkCache.delete(key); // también borra la data para liberar memoria, opcional
}
}
}
// --- Loop de render ---
function animate() {
requestAnimationFrame(animate);
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
// Control de cámara (WASD + QE)
const walkSpeed = 0.2;
if (k[87]) { // W
camera.position.x += Math.sin(xa) * walkSpeed;
camera.position.z += Math.cos(xa) * walkSpeed;
}
if (k[83]) { // S
camera.position.x -= Math.sin(xa) * walkSpeed;
camera.position.z -= Math.cos(xa) * walkSpeed;
}
if (k[65]) { // A
camera.position.x += Math.cos(xa) * walkSpeed;
camera.position.z -= Math.sin(xa) * walkSpeed;
}
if (k[68]) { // D
camera.position.x -= Math.cos(xa) * walkSpeed;
camera.position.z += Math.sin(xa) * walkSpeed;
}
if (k[69]) camera.position.y += walkSpeed; // E
if (k[81]) camera.position.y -= walkSpeed; // Q
// Actualizar luz para que siga cámara
light.position.copy(camera.position);
// Control de rotaciĂłn
if (ya < -1.5) ya = -1.5;
if (ya > 1.5) ya = 1.5;
camera.lookAt(
camera.position.x + Math.sin(xa) * Math.cos(ya),
camera.position.y + Math.sin(ya),
camera.position.z + Math.cos(xa) * Math.cos(ya)
);
animateChunksIfNeeded();
renderer.render(scene, camera);
}
let lastUpdate = 0;
function animateChunksIfNeeded() {
const now = performance.now();
if (now - lastUpdate > 500) { // actualizar cada 0.5s para no bloquear mucho
updateChunks();
lastUpdate = now;
}
}
animate();
};
</script>
And it gets laggy when the terrain generates… any solution?