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

103 lines
3.1 KiB
TypeScript

import { Object3D, Vector2, Vector3 } from 'three';
import { WorldChunk } from '../../../common/world/WorldChunk';
import { WorldManager } from '../../../common/world/WorldManager';
import { ClientWorldChunkShader } from './ClientWorldChunkShader';
import { ClientWorldTexture } from './ClientWorldTexture';
import { QuadtreeMesher } from './quadtree/quadtree-mesher';
// TODO: distance loading
const BASE = '/assets/terrain/';
export class ClientWorld extends WorldManager {
public world = new Object3D();
private _chunkMeshers: QuadtreeMesher[] = [];
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.getPointHeight(x - 1, y);
const heightR = this.getPointHeight(x + 1, y);
const heightD = this.getPointHeight(x, y - 1);
const heightU = this.getPointHeight(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(camera: Vector3) {
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 root = new QuadtreeMesher(
chunk.size,
new Vector2(chunk.size * chunk.x, chunk.size * chunk.y),
material,
);
root.getHeight = this.getHeight.bind(this);
root.getNormal = this.getNormalVector.bind(this);
root.initialize();
this._chunkMeshers.push(root);
this.world.add(root.container);
return;
}
this._chunkMeshers.forEach((item) => item.update(camera));
}
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))];
}, []);
}
}