import { Vec3 } from './vec3';
export class Octree {
    constructor(dimensions, maxObjects = 10, maxLevels = 10) {
        this.dimensions = dimensions;
        this.maxObjects = maxObjects;
        this.maxLevels = maxLevels;
        this.root = new OcTreeNode(new Vec3({
            x: 0,
            y: 0,
            z: 0
        }), this.dimensions, maxLevels, maxObjects, 0);
    }
    insert(obj) {
        this.root.insert(obj);
    }
    find(position, dimensions) {
        return this.root.find(position.x, position.y, position.z, dimensions.x, dimensions.y, dimensions.z);
    }
}
export class OcTreeNode {
    constructor(position, dimensions, maxLevels, maxObjects, currentLevel = 0) {
        this.position = position;
        this.dimensions = dimensions;
        this.maxLevels = maxLevels;
        this.maxObjects = maxObjects;
        this.currentLevel = currentLevel;
        this.objects = [];
        this.nodes = [];
    }
    insert(obj) {
        const index = this.getIndex(obj.position.x, obj.position.y, obj.position.z);
        if (index === Direction.Here) {
            this.objects.push(obj);
        }
        else {
            return this.nodes[index].insert(obj);
        }
        if (this.objects.length > this.maxObjects &&
            this.currentLevel < this.maxLevels) {
            this.split();
        }
    }
    find(x, y, z, xw, yh, zd) {
        if (this.nodes.length < 1) {
            return this.objects;
        }
        const indecies = this.getIndeciesForRect(x, y, z, xw, yh, zd);
        let results = [];
        for (let i = 0; i < indecies.length - 1; i++) {
            let res = this.nodes[indecies[i]].find(x, y, z, xw, yh, zd);
            results.push([...res]);
        }
        return results;
    }
    split() {
        const halfWidth = this.dimensions.x / 2;
        const halfHeight = this.dimensions.y / 2;
        const halfDepth = this.dimensions.z / 2;
        this.nodes[Direction.AboveUpperLeft] = new OcTreeNode(new Vec3({
            x: this.position.x,
            y: this.position.y,
            z: this.position.z + halfDepth
        }), new Vec3({
            x: halfWidth,
            y: halfHeight,
            z: halfDepth
        }), this.maxLevels, this.maxObjects, this.currentLevel++);
        this.nodes[Direction.AboveUpperRight] = new OcTreeNode(new Vec3({
            x: this.position.x + halfWidth,
            y: this.position.y,
            z: this.position.z + halfDepth
        }), new Vec3({
            x: halfWidth,
            y: halfHeight,
            z: halfDepth
        }), this.maxLevels, this.maxObjects, this.currentLevel++);
        this.nodes[Direction.AboveLowerRight] = new OcTreeNode(new Vec3({
            x: this.position.x + halfWidth,
            y: this.position.y + halfHeight,
            z: this.position.z + halfDepth
        }), new Vec3({
            x: halfWidth,
            y: halfHeight,
            z: halfDepth
        }), this.maxLevels, this.maxObjects, this.currentLevel++);
        this.nodes[Direction.AboveLowerLeft] = new OcTreeNode(new Vec3({
            x: this.position.x,
            y: this.position.y + halfHeight,
            z: this.position.z + halfDepth
        }), new Vec3({
            x: halfWidth,
            y: halfHeight,
            z: halfDepth
        }), this.maxLevels, this.maxObjects, this.currentLevel++);
        this.nodes[Direction.BelowUpperLeft] = new OcTreeNode(new Vec3({
            x: this.position.x,
            y: this.position.y,
            z: this.position.z
        }), new Vec3({
            x: halfWidth,
            y: halfHeight,
            z: halfDepth
        }), this.maxLevels, this.maxObjects, this.currentLevel++);
        this.nodes[Direction.BelowUpperRight] = new OcTreeNode(new Vec3({
            x: this.position.x + halfWidth,
            y: this.position.y,
            z: this.position.z
        }), new Vec3({
            x: halfWidth,
            y: halfHeight,
            z: halfDepth
        }), this.maxLevels, this.maxObjects, this.currentLevel++);
        this.nodes[Direction.BelowLowerRight] = new OcTreeNode(new Vec3({
            x: this.position.x + halfWidth,
            y: this.position.y + halfHeight,
            z: this.position.z
        }), new Vec3({
            x: halfWidth,
            y: halfHeight,
            z: halfDepth
        }), this.maxLevels, this.maxObjects, this.currentLevel++);
        this.nodes[Direction.BelowLowerLeft] = new OcTreeNode(new Vec3({
            x: this.position.x,
            y: this.position.y + halfHeight,
            z: this.position.z
        }), new Vec3({
            x: halfWidth,
            y: halfHeight,
            z: halfDepth
        }), this.maxLevels, this.maxObjects, this.currentLevel++);
        this.distributeObjectsToNodes();
    }
    distributeObjectsToNodes() {
        if (this.nodes.length < 8) {
            this.split();
            return;
        }
        this.objects.forEach((obj) => {
            const direction = this.getIndex(obj.position.x, obj.position.y, obj.position.z);
            this.nodes[direction].insert(obj);
        });
        this.objects = [];
    }
    getIndex(x, y, z) {
        if (this.nodes.length === 0) {
            return Direction.Here;
        }
        const halfWidth = this.dimensions.x / 2;
        const halfHeight = this.dimensions.y / 2;
        const halfDepth = this.dimensions.z / 2;
        const isBelow = z < this.position.z + halfDepth;
        const isLeft = x < this.position.x + halfWidth;
        const isUpper = y > this.position.y + halfHeight;
        if (isBelow) {
            if (isLeft) {
                if (isUpper)
                    return Direction.AboveUpperLeft;
                else
                    return Direction.AboveLowerLeft;
            }
            else {
                if (isUpper)
                    return Direction.AboveUpperRight;
                else
                    return Direction.AboveLowerRight;
            }
        }
        else {
            if (isLeft) {
                if (isUpper)
                    return Direction.BelowUpperLeft;
                else
                    return Direction.BelowLowerLeft;
            }
            else {
                if (isUpper)
                    return Direction.BelowUpperRight;
                else
                    return Direction.BelowLowerRight;
            }
        }
    }
    getIndeciesForRect(x, y, z, xw, yh, zd) {
        if (!(x > this.position.x && x < this.position.x + this.dimensions.x) ||
            !(y > this.position.y && y < this.position.y + this.dimensions.y) ||
            !(z > this.position.z && z < this.position.z + this.dimensions.z)) {
            return [];
        }
        let indecies = [];
        indecies.push(this.getIndex(x, y, z));
        indecies.push(this.getIndex(x + xw, y + yh, z + zd));
        return indecies;
    }
}
var Direction;
(function (Direction) {
    Direction[Direction["AboveUpperLeft"] = 0] = "AboveUpperLeft";
    Direction[Direction["AboveUpperRight"] = 1] = "AboveUpperRight";
    Direction[Direction["AboveLowerRight"] = 2] = "AboveLowerRight";
    Direction[Direction["AboveLowerLeft"] = 3] = "AboveLowerLeft";
    Direction[Direction["BelowUpperLeft"] = 4] = "BelowUpperLeft";
    Direction[Direction["BelowUpperRight"] = 5] = "BelowUpperRight";
    Direction[Direction["BelowLowerRight"] = 6] = "BelowLowerRight";
    Direction[Direction["BelowLowerLeft"] = 7] = "BelowLowerLeft";
    Direction[Direction["Here"] = 8] = "Here";
})(Direction || (Direction = {}));
