Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
93e062a
Fix mesh picking example
xeolabs Jun 16, 2025
7aad5a3
DTX refactorings
xeolabs Aug 5, 2025
f925065
DTX refactorings
xeolabs Aug 9, 2025
8e97bbf
WebGLRenderer refactorings
xeolabs Aug 13, 2025
934f6e5
WebGLRenderer refactorings
xeolabs Aug 15, 2025
00cd7c6
WebGLRenderer refactorings
xeolabs Aug 18, 2025
4a98c01
WebGLRenderer refactorings
xeolabs Aug 20, 2025
e8c8c26
Improve exception messages
xeolabs Aug 22, 2025
dfc6695
Refactorings
xeolabs Aug 25, 2025
a4bc3df
Refactorings
xeolabs Aug 26, 2025
598db29
Refactorings
xeolabs Aug 28, 2025
0b6341b
Refactorings
xeolabs Aug 31, 2025
d2dd3ef
Refactorings
xeolabs Sep 3, 2025
aca1526
Refactorings
xeolabs Sep 9, 2025
35732c6
Refactorings
xeolabs Sep 12, 2025
1d593c5
Refactorings
xeolabs Sep 16, 2025
923e886
Refactorings
xeolabs Sep 18, 2025
c94c687
Refactorings
xeolabs Sep 21, 2025
5668626
Refactorings
xeolabs Sep 24, 2025
7fd2704
Added initial mesh picking, refactorings
xeolabs Oct 1, 2025
05d53e3
WebGLRenderer architectural polish
xeolabs Oct 7, 2025
03e8c52
WebGLRenderer architectural polish
xeolabs Oct 8, 2025
8decafe
WebGLRenderer architectural polish
xeolabs Oct 8, 2025
9390ae2
WebGLRenderer architectural polish
xeolabs Oct 8, 2025
7a8f856
WebGLRenderer architectural polish
xeolabs Oct 8, 2025
1fe86fd
WebGLRenderer surface picking WIP
xeolabs Oct 8, 2025
42821c4
WebGLRenderer refactorings
xeolabs Oct 9, 2025
cdc2a3d
WebGLRenderer refactorings - memory and draw perf improvements
xeolabs Oct 14, 2025
c899797
WebGLRenderer refactorings
xeolabs Oct 23, 2025
75b6a5f
WebGLRenderer refactorings
xeolabs Oct 24, 2025
86afba2
Refactorings
xeolabs Nov 11, 2025
d7c1053
Improved exception management
xeolabs Nov 11, 2025
6652e23
Refactorings
xeolabs Nov 19, 2025
e649834
Refactorings, better event and memory management
xeolabs Nov 19, 2025
e379934
Refactorings, better event and memory management
xeolabs Nov 19, 2025
1595188
ViewLayer init/cleanup logic
xeolabs Nov 22, 2025
6b9da8c
Refactorings
xeolabs Nov 25, 2025
f895682
Refactorings
xeolabs Nov 25, 2025
91975a3
Refactorings
xeolabs Nov 25, 2025
38dcc76
Refactorings
xeolabs Nov 25, 2025
388507e
Game loop improvements, error msg improvements
xeolabs Nov 26, 2025
aadc3fb
Game loop improvements
xeolabs Nov 26, 2025
76c3bb7
Use improved SDK error logging in ModelConverter
xeolabs Nov 27, 2025
cb4eb62
Refactorings
xeolabs Dec 2, 2025
fe80b58
Refactorings
xeolabs Dec 4, 2025
b9fb495
Stronger math types
xeolabs Dec 18, 2025
ed64ecb
Math library refactor
xeolabs Dec 20, 2025
a6d6633
Refactorings
xeolabs Dec 22, 2025
1d9d6b6
TSDoc fixes
xeolabs Dec 25, 2025
c18686d
TSDoc fixes
xeolabs Dec 26, 2025
f114aea
TSDoc fixes
xeolabs Dec 27, 2025
6ec1d91
Various fixes
xeolabs Dec 29, 2025
d5baf30
Various fixes
xeolabs Dec 29, 2025
9d61fe8
Memory usage tracking
xeolabs Jan 1, 2026
3b7d1c4
Refactorings
xeolabs Jan 4, 2026
6cc4a55
WebGLContextRestored handling, improved debug tools
xeolabs Jan 5, 2026
1236bfe
More debug tooling
xeolabs Jan 6, 2026
95c5a8e
Refactorings
xeolabs Jan 8, 2026
466045f
Refactorings
xeolabs Jan 9, 2026
67c9933
Refactorings
xeolabs Jan 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
266 changes: 266 additions & 0 deletions packages/sdk/src/aabb/SceneAABB3Index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
import {
createAABB3Float64, collapseAABB3,
expandAABB3, expandAABB3Point3, type AABB3Float
} from "../math/boundaries";
import type {Scene, SceneMesh, SceneObject} from "../scene";
import {createVec3Float64, createVec4Float64, type Mat4, type Vec3Float, type Vec3, transformPoint4} from "../math";
import type {FloatArrayParam} from "../math";

const tempVec4a = createVec4Float64();
const tempVec4b = createVec4Float64();

/**
* Computes the world-space AABB for a set of compressed positions using a transform matrix.
* Writes the result into the given `worldAABB` array.
*/
function getPositionsWorldAABB3(
positionsCompressed: FloatArrayParam,
aabb: AABB3Float,
matrix: Mat4,
worldAABB: AABB3Float
): AABB3Float {
collapseAABB3(worldAABB);
const xScale = (aabb[3] - aabb[0]) / 65535;
const xOffset = aabb[0];
const yScale = (aabb[4] - aabb[1]) / 65535;
const yOffset = aabb[1];
const zScale = (aabb[5] - aabb[2]) / 65535;
const zOffset = aabb[2];

for (let i = 0, len = positionsCompressed.length; i < len; i += 3) {
tempVec4a[0] = positionsCompressed[i] * xScale + xOffset;
tempVec4a[1] = positionsCompressed[i + 1] * yScale + yOffset;
tempVec4a[2] = positionsCompressed[i + 2] * zScale + zOffset;
tempVec4a[3] = 1.0;
transformPoint4(matrix, tempVec4a, tempVec4b);
expandAABB3Point3(worldAABB, <Vec3>tempVec4b);
}

return worldAABB;
}

/**
* Caches and maintains axis-aligned bounding boxes (AABBs) for {@link SceneMesh} and {@link SceneObject} instances
* in a {@link Scene}, using lazy evaluation and dirty flags.
*/
export class SceneAABB3Index {

#scene: Scene;
#meshAABBs = new Map<string, AABB3Float>();
#objectAABBs = new Map<string, AABB3Float>();
#meshDirty = new Set<string>();
#objectDirty = new Set<string>();
#unsubscribers: (() => void)[] = [];
#sceneAABB: AABB3Float;
#sceneAABBDirty: boolean;
#sceneCenter: Vec3Float;

/**
* Constructs a new SceneAABB3Index for the given {@link Scene}.
* @param scene The scene to tileIndex.
*/
constructor(scene: Scene) {
this.#scene = scene;

this.#sceneAABB = createAABB3Float64();
this.#sceneCenter = createVec3Float64();
this.#sceneAABBDirty = true;

// Mark initial meshes and objects dirty
// @ts-ignore
for (const object of Object.values(scene.objects)) {
for (const mesh of object.meshes) {
this.#meshDirty.add(mesh.id);
}
this.#objectDirty.add(object.id);
}

// Subscribe to Scene events
this.#unsubscribers.push(
scene.events.onSceneObjectCreated.subscribe((_, object) => {
for (const mesh of object.meshes) {
this.#meshDirty.add(mesh.id);
}
this.#objectDirty.add(object.id);
}),

scene.events.onSceneMeshMoved.subscribe((_, mesh) => {
this.#meshDirty.add(mesh.id);
if (mesh.object) {
this.#objectDirty.add(mesh.object.id);
}
}),

scene.events.onSceneObjectDestroyed.subscribe((_, object) => {
for (const mesh of object.meshes) {
this.#meshAABBs.delete(mesh.id);
this.#meshDirty.delete(mesh.id);
}
this.#objectAABBs.delete(object.id);
this.#objectDirty.delete(object.id);
}),

scene.events.onSceneModelDestroyed.subscribe((_, model) => {
// @ts-ignore
for (const object of Object.values(model.objects)) {
for (const mesh of object.meshes) {
this.#meshAABBs.delete(mesh.id);
this.#meshDirty.delete(mesh.id);
}
this.#objectAABBs.delete(object.id);
this.#objectDirty.delete(object.id);
}
})
);
}

#getMeshAABB(mesh: SceneMesh): AABB3Float {
let aabb = this.#meshAABBs.get(mesh.id);
if (!aabb) {
aabb = createAABB3Float64();
this.#meshAABBs.set(mesh.id, aabb);
}

if (this.#meshDirty.has(mesh.id)) {
getPositionsWorldAABB3(
mesh.geometry.positionsCompressed,
mesh.geometry.aabb,
mesh.matrix,
aabb
);
this.#meshDirty.delete(mesh.id);
}

return aabb;
}

#getObjectAABB(objectId: string): AABB3Float | null {
const object = this.#scene.objects[objectId];
if (!object) return null;

let aabb = this.#objectAABBs.get(objectId);
if (!aabb) {
aabb = createAABB3Float64();
this.#objectAABBs.set(objectId, aabb);
}

if (this.#objectDirty.has(objectId)) {
collapseAABB3(aabb);
let found = false;
for (const mesh of object.meshes) {
const meshAABB = this.#getMeshAABB(mesh);
expandAABB3(aabb, meshAABB);
found = true;
}
if (!found) {
this.#objectAABBs.delete(objectId);
return null;
}
this.#objectDirty.delete(objectId);
}

return aabb;
}

/**
* Gets the combined axis-aligned bounding box (AABB) of the entire scene.
*/
getSceneAABB(): AABB3Float {
if (this.#objectDirty.size > 0) {
collapseAABB3(this.#sceneAABB);
// @ts-ignore
for (const object of Object.values(this.#scene.objects)) {
const aabb = this.#getObjectAABB(object.id);
if (aabb) {
expandAABB3(this.#sceneAABB, aabb);
}
}
this.#sceneAABBDirty = false;
}
return this.#sceneAABB;
}

/**
* Gets the center of the scene's AABB.
*/
getSceneCenter(): Vec3Float {
if (this.#sceneAABBDirty) {
this.getSceneAABB();
}
this.#sceneCenter[0] = (this.#sceneAABB[0] + this.#sceneAABB[3]) * 0.5;
this.#sceneCenter[1] = (this.#sceneAABB[1] + this.#sceneAABB[4]) * 0.5;
this.#sceneCenter[2] = (this.#sceneAABB[2] + this.#sceneAABB[5]) * 0.5;
this.#sceneCenter[3] = 1.0; // Homogeneous coordinate
return this.#sceneCenter;
}

/**
* Gets the combined AABB of the given {@link SceneObject} IDs.
* Only includes objects that are currently registered and valid.
*
* @param objectIds The list of SceneObject IDs.
* @returns Combined AABB, or `null` if none found.
*/
getCombinedObjectAABB(objectIds: string[]): AABB3Float | null {
const result = createAABB3Float64();
collapseAABB3(result);
let foundAny = false;

for (const objectId of objectIds) {
const aabb = this.#getObjectAABB(objectId);
if (aabb) {
expandAABB3(result, aabb);
foundAny = true;
}
}

return foundAny ? result : null;
}

/**
* Gets the cached or computed AABB of a single {@link SceneObject}, if available.
*
* @param objectId The SceneObject ID.
* @returns AABB or `null` if the object does not exist or has no meshes.
*/
getObjectAABB(objectId: string): AABB3Float | null {
return this.#getObjectAABB(objectId);
}

/**
* Destroys this tileIndex and releases internal resources.
*
* - Clears cached AABBs and dirty flags.
* - Unsubscribes from all Scene event listeners.
*/
destroy(): void {
this.#meshAABBs.clear();
this.#objectAABBs.clear();
this.#meshDirty.clear();
this.#objectDirty.clear();

for (const unsubscribe of this.#unsubscribers) {
unsubscribe();
}
this.#unsubscribers.length = 0;
}
}


const sceneIndexes = {};

/**
*
* @param scene
*/
export function getSceneAABBIndex(scene: Scene) {
let sceneIndex = sceneIndexes[scene.id];
if (!sceneIndex) {
sceneIndex = sceneIndexes[scene.id] = new SceneAABB3Index(scene);
scene.events.onSceneDestroyed.sub((scene, _) => {
sceneIndex.destroy();
delete sceneIndexes[scene.id];
});
}
return sceneIndex;
}
59 changes: 59 additions & 0 deletions packages/sdk/src/aabb/createSceneObjectAABB3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {type SceneObject} from "../scene";
import {collapseAABB3, createAABB3Float64, expandAABB3, expandAABB3Point3, type AABB3} from "../math/boundaries";
import {createVec4Float64, type Mat4, transformPoint4, type Vec3} from "../math";
import {type FloatArrayParam} from "../math";

const tempVec4a = createVec4Float64();
const tempVec4b = createVec4Float64();
const tempAABB3a = createAABB3Float64();

/**
* Creates an Axis-Aligned Bounding Box (AABB) for a given scene object.
*
* @param {SceneObject} sceneObject - The scene object for which the AABB is created.
* @param {FloatArrayParam} [aabb=createAABBFloat64()] - The initial AABB to be modified. Defaults to a collapsed AABB.
* @returns {FloatArrayParam} - The resulting AABB for the scene object.
*/
export function createSceneObjectAABB3(sceneObject: SceneObject, aabb: AABB3 = createAABB3Float64()): AABB3 {
collapseAABB3(aabb);
let found = false;
for (const mesh of sceneObject.meshes) {
expandAABB3(aabb, getPositionsWorldAABB3(
mesh.geometry.positionsCompressed,
mesh.geometry.aabb,
mesh.matrix,
tempAABB3a
));
found = true;
}
if (!found) {
(<Float64Array<any>>aabb).set([0, 0, 0, 0, 0, 0]);
}
return aabb;
}

function getPositionsWorldAABB3(
positionsCompressed: FloatArrayParam,
aabb: AABB3,
matrix: Mat4,
worldAABB: AABB3
): AABB3 {

const xScale = (aabb[3] - aabb[0]) / 65535;
const xOffset = aabb[0];
const yScale = (aabb[4] - aabb[1]) / 65535;
const yOffset = aabb[1];
const zScale = (aabb[5] - aabb[2]) / 65535;
const zOffset = aabb[2];

for (let i = 0, len = positionsCompressed.length; i < len; i += 3) {
tempVec4a[0] = positionsCompressed[i] * xScale + xOffset;
tempVec4a[1] = positionsCompressed[i + 1] * yScale + yOffset;
tempVec4a[2] = positionsCompressed[i + 2] * zScale + zOffset;
tempVec4a[3] = 1.0;
transformPoint4(matrix, tempVec4a, tempVec4b);
expandAABB3Point3(worldAABB, <Vec3>tempVec4b);
}

return aabb;
}
Loading