# src/collision/ObjectsKdTree3.js

    import {math} from "@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js";

    const MAX_KD_TREE_DEPTH = 15; // Increase if greater precision needed
    const kdTreeDimLength = new Float32Array(3);

    /**
     * A k-d tree for World-space 3D Entity collision detection with AABBs and frustums.
     */
    export class ObjectsKdTree3 {

        constructor(cfg) {

            if (!cfg) {
                throw "Parameter expected: cfg";
            }

            if (!cfg.viewer) {
                throw "Parameter expected: cfg.viewer";
            }

            this.viewer = cfg.viewer;

            this._maxTreeDepth = cfg.maxTreeDepth || MAX_KD_TREE_DEPTH;
            this._root = null;
            this._needsRebuild = true;

            this._onModelLoaded = this.viewer.scene.on("modelLoaded", (modelId) => {
                this._needsRebuild = true;
            });

            this._onModelUnloaded = this.viewer.scene.on("modelUnloaded", (modelId) => {
                this._needsRebuild = true;
            });
        }

        /**
         * Gets the root ObjectsKdTree3 node.
         */
        get root() {
            if (this._needsRebuild) {
                this._rebuild();
            }
            return this._root;
        }

        _rebuild() {
            const viewer = this.viewer;
            const scene = viewer.scene;
            const depth = 0;
            this._root = {
                aabb: scene.getAABB()
            };
            for (let objectId in scene.objects) {
                const entity = scene.objects[objectId];
                this._insertEntity(this._root, entity, depth + 1);
            }
            this._needsRebuild = false;
        }

        _insertEntity(node, entity, depth) {

            const entityAABB = entity.aabb;

            if (depth >= this._maxTreeDepth) {
                node.entities = node.entities || [];
                node.entities.push(entity);
                return;
            }
            if (node.left) {
                if (math.containsAABB3(node.left.aabb, entityAABB)) {
                    this._insertEntity(node.left, entity, depth + 1);
                    return;
                }
            }
            if (node.right) {
                if (math.containsAABB3(node.right.aabb, entityAABB)) {
                    this._insertEntity(node.right, entity, depth + 1);
                    return;
                }
            }
            const nodeAABB = node.aabb;
            kdTreeDimLength[0] = nodeAABB[3] - nodeAABB[0];
            kdTreeDimLength[1] = nodeAABB[4] - nodeAABB[1];
            kdTreeDimLength[2] = nodeAABB[5] - nodeAABB[2];
            let dim = 0;
            if (kdTreeDimLength[1] > kdTreeDimLength[dim]) {
                dim = 1;
            }
            if (kdTreeDimLength[2] > kdTreeDimLength[dim]) {
                dim = 2;
            }
            if (!node.left) {
                const aabbLeft = nodeAABB.slice();
                aabbLeft[dim + 3] = ((nodeAABB[dim] + nodeAABB[dim + 3]) / 2.0);
                node.left = {
                    aabb: aabbLeft
                };
                if (math.containsAABB3(aabbLeft, entityAABB)) {
                    this._insertEntity(node.left, entity, depth + 1);
                    return;
                }
            }
            if (!node.right) {
                const aabbRight = nodeAABB.slice();
                aabbRight[dim] = ((nodeAABB[dim] + nodeAABB[dim + 3]) / 2.0);
                node.right = {
                    aabb: aabbRight
                };
                if (math.containsAABB3(aabbRight, entityAABB)) {
                    this._insertEntity(node.right, entity, depth + 1);
                    return;
                }
            }
            node.entities = node.entities || [];
            node.entities.push(entity);
        }

        /**
         * Destroys this ObjectsKdTree3.
         */
        destroy() {
            const scene = this.viewer.scene;
            scene.off(this._onModelLoaded);
            scene.off(this._onModelUnloaded);
            this._root = null;
            this._needsRebuild = true;
        }
    }