103 lines
3.1 KiB
TypeScript
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.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(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.getInterpolatedHeight.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))];
|
|
}, []);
|
|
}
|
|
}
|