icy3dw/src/client/object/world/ClientWorld.ts

99 lines
2.9 KiB
TypeScript

import { Mesh, Object3D, Vector3 } from 'three';
import { WorldChunk } from '../../../common/world/WorldChunk';
import { WorldManager } from '../../../common/world/WorldManager';
import { ClientWorldChunkShader } from './ClientWorldChunkShader';
import { ClientWorldMesher } from './ClientWorldMesher';
import { ClientWorldTexture } from './ClientWorldTexture';
// TODO: distance loading
// TODO: LOD
const BASE = '/assets/terrain/';
export class ClientWorld extends WorldManager {
public world = new Object3D();
private _mesher = new ClientWorldMesher();
private _chunkMeshes: Mesh[] = [];
private _chunkMeshQueue: WorldChunk[] = [];
private _worldTextures: Map<string, ClientWorldTexture> = new Map();
private _shader = new ClientWorldChunkShader(this._worldTextures);
getNormalVector(x: number, y: number): Vector3 {
const heightL = this.getHeight(x - 1, y);
const heightR = this.getHeight(x + 1, y);
const heightD = this.getHeight(x, y - 1);
const heightU = this.getHeight(x, y + 1);
const normalized = new Vector3(heightL - heightR, 2, heightD - heightU);
normalized.normalize();
return normalized;
}
async initialize() {
await this.loadWorld();
const noiseFile = `${BASE}/texture/${this.manifest.textureBombingNoise}`;
await this.loadTextureList([
noiseFile,
...this._chunks.map(
(chunk) => `${BASE}/region/splat-${chunk.x}-${chunk.y}.png`,
),
...this.gatherManifestTextures(),
]);
this._shader.initialize();
this._shader.setNoise(this._worldTextures.get(noiseFile));
this.createMeshes();
}
async loadTexture(src: string): Promise<ClientWorldTexture> {
if (this._worldTextures.has(src)) {
return this._worldTextures.get(src);
}
const tex = await ClientWorldTexture.loadTexture(src);
this._worldTextures.set(src, tex);
return tex;
}
async loadTextureList(srcList: string[]): Promise<void> {
for (const src of srcList) {
await this.loadTexture(src);
}
}
update(dt: number) {
if (this._chunkMeshQueue.length) {
const chunk = this._chunkMeshQueue.shift();
const material = this._shader.getShader(
chunk,
`${BASE}/region/splat-${chunk.x}-${chunk.y}.png`,
chunk.region.splat.map((file) => `${BASE}/texture/${file}`),
);
const mesh = this._mesher.createTerrainMesh(
chunk,
material,
this.getHeight.bind(this),
this.getNormalVector.bind(this),
);
this._chunkMeshes.push(mesh);
this.world.add(mesh);
}
}
private createMeshes() {
this._chunks.forEach((chunk) => {
this._chunkMeshQueue.push(chunk);
});
}
private gatherManifestTextures(): string[] {
return this.manifest.regionMap.reduce<string[]>((list, entry) => {
const paths = entry.splat.map((file) => `${BASE}/texture/${file}`);
return [...list, ...paths.filter((path) => !list.includes(path))];
}, []);
}
}