In the process of developing an innovative game centered around cubes in a virtual sandbox environment (a groundbreaking concept poised to revolutionize the gaming industry), I am currently focusing on chunk generation. This is a glimpse into my progress thus far:
The blocks are defined within an object literal:
import * as THREE from 'three';
const loader = new THREE.TextureLoader();
interface BlockAttrs
{
breakable: boolean;
empty: boolean;
}
export interface Block
{
attrs: BlockAttrs;
mat_bottom?: THREE.MeshBasicMaterial;
mat_side?: THREE.MeshBasicMaterial;
mat_top?: THREE.MeshBasicMaterial;
}
interface BlockList
{
[key: string]: Block;
}
export const Blocks: BlockList = {
air:
{
attrs:
{
breakable: false,
empty: true,
},
},
grass:
{
attrs:
{
breakable: true,
empty: false,
},
mat_bottom: new THREE.MeshBasicMaterial({map: loader.load("/tex/dirt.png")}),
mat_side: new THREE.MeshBasicMaterial({map: loader.load("/tex/grass-side.png")}),
mat_top: new THREE.MeshBasicMaterial({map: loader.load("/tex/grass-top.png")}),
},
};
Introducing my Chunk
class:
import * as THREE from 'three';
import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils';
import { Block, Blocks } from './blocks';
const px = 0; const nx = 1; const py = 2;
const ny = 3; const pz = 4; const nz = 5;
export default class Chunk
{
private static readonly faces = [
new THREE.PlaneGeometry(1,1)
.rotateY(Math.PI / 2)
.translate(0.5, 0, 0),
new THREE.PlaneGeometry(1,1)
.rotateY(-Math.PI / 2)
.translate(-0.5, 0, 0),
new THREE.PlaneGeometry(1,1)
.rotateX(-Math.PI / 2)
.translate(0, 0.5, 0),
new THREE.PlaneGeometry(1,1)
.rotateX(Math.PI / 2)
.translate(0, -0.5, 0),
new THREE.PlaneGeometry(1,1)
.translate(0, 0, 0.5),
new THREE.PlaneGeometry(1,1)
.rotateY(Math.PI)
.translate(0, 0, -0.5)
];
private structure: Array<Array<Array<Block>>>;
public static readonly size = 16;
private materials = Array<THREE.MeshBasicMaterial>();
private terrain = Array<THREE.BufferGeometry>();
constructor ()
{
this.structure = new Array<Array<Array<Block>>>(Chunk.size);
for (let x = 0; x < Chunk.size; x++)
{
this.structure[x] = new Array<Array<Block>>(Chunk.size);
for (let y = 0; y < Chunk.size; y++)
{
this.structure[x][y] = new Array<Block>(Chunk.size);
for (let z = 0; z < Chunk.size; z++)
if ((x+y+z) % 2)
this.structure[x][y][z] = Blocks.grass;
else
this.structure[x][y][z] = Blocks.air;
}
}
}
private blockEmpty (x: number, y: number, z: number): boolean
{
let empty = true;
if (
x >= 0 && x < Chunk.size &&
y >= 0 && y < Chunk.size &&
z >= 0 && z < Chunk.size
) {
empty = this.structure[x][y][z].attrs.empty;
}
return empty;
}
private generateBlockFaces (x: number, y: number, z: number): void
{
if (this.blockEmpty(x+1, y, z))
{
this.terrain.push(Chunk.faces[px].clone().translate(x, y, z));
this.materials.push(this.structure[x][y][z].mat_side);
}
if (this.blockEmpty(x, y, z+1))
{
this.terrain.push(Chunk.faces[nx].clone().translate(x, y, z));
this.materials.push(this.structure[x][y][z].mat_side);
}
if (this.blockEmpty(x, y-1, z))
{
this.terrain.push(Chunk.faces[py].clone().translate(x, y, z));
this.materials.push(this.structure[x][y][z].mat_bottom);
}
if (this.blockEmpty(x, y+1, z))
{
this.terrain.push(Chunk.faces[ny].clone().translate(x, y, z));
this.materials.push(this.structure[x][y][z].mat_top);
}
if (this.blockEmpty(x, y, z-1))
{
this.terrain.push(Chunk.faces[pz].clone().translate(x, y, z));
this.materials.push(this.structure[x][y][z].mat_side);
}
if (this.blockEmpty(x-1, y, z))
{
this.terrain.push(Chunk.faces[nz].clone().translate(x, y, z));
this.materials.push(this.structure[x][y][z].mat_side);
}
}
public generateTerrain (): THREE.Mesh
{
this.terrain = new Array<THREE.BufferGeometry>();
this.materials = new Array<THREE.MeshBasicMaterial>();
for (let x = 0; x < Chunk.size; x++)
for (let y = 0; y < Chunk.size; y++)
for (let z = 0; z < Chunk.size; z++)
if (!this.structure[x][y][z].attrs.empty)
this.generateBlockFaces(x, y, z);
return new THREE.Mesh(
BufferGeometryUtils.mergeBufferGeometries(this.terrain),
this.materials
);
}
}
I acknowledge the need to separate the mesh creation logic from the model, but currently, I am exploring different approaches. The functionality of the class operates as follows:
Initially, the constructor()
method establishes a 3D matrix of Block
entities. It is configured to generate these entities in a checkerboard pattern of air
and grass
, resulting in every other block being empty.
Subsequently, I invoke generateTerrain()
within my Scene:
this.chunk = new Chunk();
this.add(this.chunk.generateTerrain());
Upon calling this method, it iterates through each non-empty block, invoking generateBlockFaces
to populate the appropriate PlaneGeometry
s in the terrain
array along with their respective THREE.MeshBasicMaterial
counterparts in the materials
array. The geometries are merged using
BufferGeometryUtils.mergeBufferGeometries</code, creating a mesh by combining the merged geometry with the <code>materials
array.
Experiencing difficulties arise when attempting to create the mesh using the materials
array, unlike with new THREE.MeshNormalMaterial
or any alternative material parameters which work seamlessly. While the object is successfully constructed when passing the materials
array (confirmed via console.log
outputs without errors), it fails to display within the scene.
Is my assumption that the materials
array assigns a material to each face incorrect? What could be the root cause of this issue?