icy3dw/src/client/object/world/quadtree/quadtree-node.ts

288 lines
7.0 KiB
TypeScript

import {
BufferGeometry,
Float32BufferAttribute,
Mesh,
Vector2,
Vector3,
} from 'three';
import { QuadtreeMesher } from './quadtree-mesher';
export enum LODQuadrant {
TOP_LEFT,
TOP_RIGHT,
BOTTOM_RIGHT,
BOTTOM_LEFT,
}
export enum LODSide {
TOP,
RIGHT,
BOTTOM,
LEFT,
}
export function mirrorSide(x: number): number {
return (x + 2) % 4;
}
export function isAdjacent(s: number, q: number): boolean {
return (4 + q - s) % 4 <= 1;
}
export function reflectSide(s: number, q: number): number {
return s % 2 ? (q % 2 ? q - 1 : q + 1) : 3 - q;
}
export class QuadtreeNode {
public _children: QuadtreeNode[] = [];
public _neighbors: QuadtreeNode[] = [];
private _leaf = true;
private _mesh?: Mesh;
constructor(
public root: QuadtreeMesher,
public parent: QuadtreeNode,
public level: number,
public quadrant: number,
public position: Vector2,
) {}
public dispose() {
if (this._mesh) {
this._destroyMesh();
}
this._children.forEach((child) => child.dispose());
this._children.length = 0;
}
public isMeshed() {
return !!this._mesh;
}
public update(camera: Vector3) {
if (this.root.actionsLeft === 0) {
return;
}
const size = this.root.size / Math.pow(2, this.level);
const abs = new Vector3(
this.position.x + size / 2,
camera.y,
this.position.y + size / 2,
);
if (this._leaf) {
if (abs.distanceTo(camera) < size && this._canSubdivide()) {
this._subdivide();
this.root.actionsLeft -= 1;
return;
}
if (!this._mesh) {
this._createMesh();
this.root.actionsLeft -= 1;
return;
}
} else if (!this._leaf) {
if (abs.distanceTo(camera) > size) {
this._merge();
this.root.actionsLeft -= 1;
return;
}
if (
this.isMeshed() &&
this._children.every((child) => child.isMeshed())
) {
this._destroyMesh();
}
this._children.forEach((child) => child.update(camera));
}
}
public setNeighbor(side: LODSide, neighbor: QuadtreeNode) {
this._neighbors[side] = neighbor;
neighbor._neighbors[mirrorSide(side)] = this;
}
public findNeighbor(side: LODSide) {
if (!this._neighbors[side] && this.parent && this.parent._neighbors[side]) {
const neighbor =
this.parent._neighbors[side]?._children[
reflectSide(side, this.quadrant)
];
if (neighbor) {
this.setNeighbor(side, neighbor);
} else {
return;
}
}
if (!this._leaf) {
for (let i = 0; i < 4; i++) {
if (isAdjacent(side, i)) {
this._children[i].findNeighbor(side);
}
}
}
}
private _createGeometry(): BufferGeometry {
const apparentSize = this.root.size / Math.pow(2, this.level);
const vertCount =
this.root.size / Math.pow(2, this.root.maxDepth - this.level);
const divisionLevel = Math.pow(2, this.level);
const geometry = new BufferGeometry();
const vertices = [];
const normals = [];
const indices = [];
const uvs = [];
for (let x = 0; x < vertCount; x++) {
for (let y = 0; y < vertCount; y++) {
const vertDivj = y / (vertCount - 1);
const vertDivi = x / (vertCount - 1);
const absX = this.position.x + (y / (vertCount - 1)) * apparentSize;
const absY = this.position.y + (x / (vertCount - 1)) * apparentSize;
const normal = this.root.getNormal(absX, absY);
// Calculate relative resolution
const pj = vertDivj * (this.root.size / divisionLevel);
const pi = vertDivi * (this.root.size / divisionLevel);
vertices.push(pj, this.root.getHeight(absX, absY), pi);
normals.push(normal.x, normal.y, normal.z);
uvs.push(absX / (this.root.size - 1), absY / (this.root.size - 1));
}
}
for (let x = 0; x < vertCount - 1; x++) {
for (let y = 0; y < vertCount - 1; y++) {
const topLeft = x * vertCount + y;
const topRight = topLeft + 1;
const bottomLeft = (x + 1) * vertCount + y;
const bottomRight = bottomLeft + 1;
indices.push(
topLeft,
bottomLeft,
topRight,
topRight,
bottomLeft,
bottomRight,
);
}
}
geometry.setIndex(indices);
geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3));
geometry.setAttribute('normal', new Float32BufferAttribute(normals, 3));
geometry.setAttribute('uv', new Float32BufferAttribute(uvs, 2));
return geometry;
}
private _merge(): boolean {
if (this._leaf) {
return false;
}
this._children.forEach((child) => child.dispose());
this._children.length = 0;
this._leaf = true;
return true;
}
private _subdivide(): boolean {
if (!this._canSubdivide()) {
return false;
}
const level = this.level + 1;
const stepLeft = this.root.size / Math.pow(2, level);
const stepForward = this.root.size / Math.pow(2, level);
const { x, y } = this.position;
// Create children
this._children[LODQuadrant.TOP_LEFT] = new QuadtreeNode(
this.root,
this,
level,
LODQuadrant.TOP_LEFT,
new Vector2(x, y),
);
this._children[LODQuadrant.TOP_RIGHT] = new QuadtreeNode(
this.root,
this,
level,
LODQuadrant.TOP_RIGHT,
new Vector2(stepLeft + x, y),
);
this._children[LODQuadrant.BOTTOM_RIGHT] = new QuadtreeNode(
this.root,
this,
level,
LODQuadrant.BOTTOM_RIGHT,
new Vector2(stepLeft + x, stepForward + y),
);
this._children[LODQuadrant.BOTTOM_LEFT] = new QuadtreeNode(
this.root,
this,
level,
LODQuadrant.BOTTOM_LEFT,
new Vector2(x, stepForward + y),
);
// Set sibling neighbors
this._children[LODQuadrant.TOP_LEFT].setNeighbor(
LODSide.RIGHT,
this._children[LODQuadrant.TOP_RIGHT],
);
this._children[LODQuadrant.TOP_RIGHT].setNeighbor(
LODSide.BOTTOM,
this._children[LODQuadrant.BOTTOM_RIGHT],
);
this._children[LODQuadrant.BOTTOM_RIGHT].setNeighbor(
LODSide.LEFT,
this._children[LODQuadrant.BOTTOM_LEFT],
);
this._children[LODQuadrant.BOTTOM_LEFT].setNeighbor(
LODSide.TOP,
this._children[LODQuadrant.TOP_LEFT],
);
// set adjacent neighbors
for (let i = 0; i < 4; i++) {
if (this._neighbors[i] && !this._neighbors[i]._leaf) {
this._neighbors[i].findNeighbor(mirrorSide(i));
}
}
this._leaf = false;
return true;
}
private _createMesh() {
if (this._mesh) {
this._destroyMesh();
}
const geometry = this._createGeometry();
const mesh = new Mesh(geometry, this.root.material);
mesh.position.set(this.position.x, 0, this.position.y);
this.root.container.add(mesh);
this._mesh = mesh;
}
private _destroyMesh() {
this.root.container.remove(this._mesh);
this._mesh = null;
}
private _canSubdivide() {
return this._leaf && this.level < this.root.maxDepth - 1;
}
}