viewer/octree.js
import * as mat4 from "./glmatrix/mat4.js";
import * as mat3 from "./glmatrix/mat3.js";
import * as vec3 from "./glmatrix/vec3.js";
import * as vec4 from "./glmatrix/vec4.js";
import {Utils} from "./utils.js";
/**
* Octree implementation targeted towards being used in the TilingLayer, could possibly be retrofitted to be a generic Octree to be used in other contexts
*/
class OctreeNode {
constructor(viewer, parent, id, min, max, level, globalTranslationVector) {
this.viewer = viewer;
this.parent = parent;
this.globalTranslationVector = globalTranslationVector;
if (parent != null) {
if (level > this.parent.deepestLevel) {
this.parent.deepestLevel = level;
}
}
this.id = id;
this.leaf = true;
this.min = min;
this.max = max;
this.width = max[0] - min[0];
this.height = max[1] - min[1];
this.depth = max[2] - min[2];
this.nrObjects = 0;
this.level = level;
this.box = new Box(this.min, this.max, this.level, globalTranslationVector);
this.minimalBox = new Box(vec3.fromValues(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE), vec3.fromValues(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE), this.level, globalTranslationVector);
// TODO also keep track of the minimal bounds (usually smaller than the "static" bounds of the node), which can be used for (frustum) occlusion culling
// TODO also keep track of the minimal bounds inc. children (useful for hierarchical culling)
this.quadrants = [];
// if (this.viewer.vertexQuantization) {
// this.vertexQuantizationMatrix = Utils.toArray(this.viewer.vertexQuantization.getTransformedQuantizationMatrix(this.bounds));
// this.vertexUnquantizationMatrix = Utils.toArray(this.viewer.vertexQuantization.getTransformedInverseQuantizationMatrix(this.bounds));
// }
this.largestFaceArea = Utils.getLargestFaceArea(this.width, this.height, this.depth);
this.largestEdge = Utils.getLargestEdge(this.width, this.height, this.depth);
}
traverseBreathFirstInternal(fn, level) {
if (this.level == level) {
var result = fn(this);
}
if (this.level >= level) {
return;
}
for (var node of this.quadrants) {
if (node != null) {
node.traverseBreathFirstInternal(fn, level);
}
}
}
traverseBreathFirst(fn) {
if (this.levelLists == null) {
return;
}
for (var l=0; l<=this.maxDepth; l++) {
for (var node of this.levelLists[l]) {
fn(node);
}
}
}
traverse(fn, onlyLeafs=false, level=0, extraArgument) {
if (!onlyLeafs || this.leaf == true) {
if (fn(this, level || 0, extraArgument) === false) {
return;
}
}
if (this.quadrants == null) {
return;
}
for (var node of this.quadrants) {
if (node != null) {
node.traverse(fn, onlyLeafs, (level || 0) + 1, extraArgument);
}
}
}
getQuadrant(localId) {
if (localId < 0 || localId > 7) {
throw "Invalid local id: " + localId;
}
var quadrant = this.quadrants[localId];
if (quadrant == null) {
var newLevel = this.level + 1;
var newId = this.id * 8 + localId + 1;
let newMin = vec3.clone(this.min);
let newMax = vec3.create();
let halfWidth = this.width / 2;
let halfHeight = this.height / 2;
let halfDepth = this.depth / 2;
switch (localId) {
case 0:
break;
case 1:
vec3.add(newMin, newMin, [0, 0, halfDepth]);
break;
case 2:
vec3.add(newMin, newMin, [0, halfHeight, 0]);
break;
case 3:
vec3.add(newMin, newMin, [0, halfHeight, halfDepth])
break;
case 4:
vec3.add(newMin, newMin, [halfWidth, 0, 0])
break;
case 5:
vec3.add(newMin, newMin, [halfWidth, 0, halfDepth])
break;
case 6:
vec3.add(newMin, newMin, [halfWidth, halfHeight, 0])
break;
case 7:
vec3.add(newMin, newMin, [halfWidth, halfHeight, halfDepth])
break;
}
var half = vec3.create();
vec3.sub(half, this.max, this.min);
vec3.div(half, half, [2, 2, 2]);
vec3.add(newMax, newMin, half);
quadrant = new OctreeNode(this.viewer, this, newId, newMin, newMax, newLevel, this.globalTranslationVector);
this.quadrants[localId] = quadrant;
}
this.leaf = false;
return quadrant;
}
getNodeById(id) {
if (id == this.id) {
return this;
}
var localId = (id - 1) % 8;
var parentId = (id - 1 - localId) / 8;
var list = [];
list.push((id - 1) % 8);
while (parentId > 0) {
list.push((parentId - 1) % 8);
parentId = Math.floor((parentId - 1) / 8);
}
var node = this;
for (var i=list.length-1; i>=0; i--) {
node = node.getQuadrant(list[i]);
}
if (node.id != id) {
throw "Ids do not match " + node.id + " / " + id;
}
return node;
}
prepareLevelListsInternal(level, levelList) {
if (this.level == level) {
levelList.push(this);
}
if (this.level >= level) {
return;
}
for (var node of this.quadrants) {
if (node != null) {
node.prepareLevelListsInternal(level, levelList);
}
}
}
prepareBreathFirstInternal(breathFirstList, fn, level) {
if (this.level == level) {
if (fn(this)) {
breathFirstList.push(this);
}
}
if (this.level > level) {
return;
}
if (this.quadrants == null) {
return;
}
for (var node of this.quadrants) {
if (node != null) {
node.prepareBreathFirstInternal(breathFirstList, fn, level);
}
}
}
prepareFullListInternal(fullList, level) {
if (this.level == level) {
fullList.push(this);
}
if (this.level > level) {
return;
}
if (this.quadrants == null) {
return;
}
for (var node of this.quadrants) {
node.prepareFullListInternal(fullList, level);
}
}
get center() {
debugger;
}
get normalizedMatrix() {
debugger;
}
get normalizedMinVector() {
debugger;
}
get normalizedMaxVector() {
debugger;
}
get radius() {
debugger;
}
get minmax() {
debugger;
}
get matrix() {
debugger;
}
}
class Box {
constructor(min, max, level, globalTranslationVector) {
this.min = min;
this.max = max;
this.level = level;
this.globalTranslationVector = globalTranslationVector;
this.update();
}
update() {
this.width = this.max[0] - this.min[0];
this.height = this.max[1] - this.min[1];
this.depth = this.max[2] - this.min[2];
this.sizeFactor = 1 / Math.pow(2, this.level);
this.center = vec3.create();
vec3.add(this.center, this.max, this.min);
vec3.div(this.center, this.center, [2, 2, 2]);
this.normalizedCenter = vec4.create();
vec3.add(this.normalizedCenter, this.center, this.globalTranslationVector);
this.radius = (Math.sqrt(Math.pow(this.width, 2) + Math.pow(this.height, 2) + Math.pow(this.depth, 2))) / 2;
this.matrix = mat4.create();
mat4.translate(this.matrix, this.matrix, this.center);
mat4.scale(this.matrix, this.matrix, [this.width, this.height, this.depth]);
this.normalizedMatrix = mat4.create();
mat4.translate(this.normalizedMatrix, this.normalizedMatrix, this.center);
mat4.translate(this.normalizedMatrix, this.normalizedMatrix, this.globalTranslationVector);
mat4.scale(this.normalizedMatrix, this.normalizedMatrix, [this.width, this.height, this.depth]);
this.normalizedMinVector = vec3.clone(this.min);
this.normalizedMaxVector = vec3.clone(this.max);
vec3.add(this.normalizedMinVector, this.normalizedMinVector, this.globalTranslationVector);
vec3.add(this.normalizedMaxVector, this.normalizedMaxVector, this.globalTranslationVector);
this.minmax = [[this.normalizedMinVector[0], this.normalizedMinVector[1], this.normalizedMinVector[2]], [this.normalizedMaxVector[0] - this.normalizedMinVector[0], this.normalizedMaxVector[1] - this.normalizedMinVector[1], this.normalizedMaxVector[2] - this.normalizedMinVector[2]]];
}
set(min, max) {
this.min = min;
this.max = max;
this.update();
}
integrate(min, max) {
vec3.min(this.min, this.min, min);
vec3.max(this.max, this.max, max);
this.update();
}
}
export class Octree extends OctreeNode {
constructor(viewer, realBounds, globalTranslationVector, maxDepth) {
super(viewer, null, 0, [realBounds[0], realBounds[1], realBounds[2]], [realBounds[3], realBounds[4], realBounds[5]], 0, globalTranslationVector);
this.maxDepth = maxDepth;
this.actualMaxLevel;
this.level = 0;
this.breathFirstList = [];
}
extractBreathFirstList(fn) {
var list = [];
this.traverseBreathFirst((node) => {
if (fn(node)) {
list.push(node);
return true;
} else {
return false;
}
});
return list;
}
traverseBreathFirstCached(fn) {
for (var node of this.breathFirstList) {
fn(node);
}
}
prepareLevelLists() {
this.levelLists = [];
for (var l=0; l<=this.maxDepth; l++) {
this.levelLists[l] = [];
this.prepareLevelListsInternal(l, this.levelLists[l]);
}
}
}