import { Vector3, Ray } from '@zephyr3d/base';
import { OctreeNode } from './octree.js';

const tmpV3 = new Vector3();
/**
 * Visitor for raycasting in the scene. It can be used for picking or other raycast-based queries.
 * @public
 */ class RaycastVisitor {
    /** @internal */ _ray;
    /** @internal */ _rayLocal;
    /** @internal */ _intersected;
    /** @internal */ _intersectedDist;
    /** @internal */ _intersectedPoint;
    constructor(ray, length){
        this._ray = ray ?? new Ray();
        this._rayLocal = new Ray();
        this._intersected = null;
        this._intersectedDist = length ?? Infinity;
        this._intersectedPoint = new Vector3();
    }
    get ray() {
        return this._ray;
    }
    set ray(ray) {
        this._ray.set(ray.origin, ray.direction);
        this._intersected = null;
        this._intersectedDist = Infinity;
    }
    get rayLength() {
        return this._intersectedDist;
    }
    set rayLength(length) {
        this._intersectedDist = length;
        this._intersected = null;
    }
    get intersected() {
        return this._intersected;
    }
    get intersectedDist() {
        return this._intersectedDist;
    }
    get intersectedPoint() {
        return this._intersectedPoint;
    }
    visit(target) {
        if (target instanceof OctreeNode) {
            return this.visitOctreeNode(target);
        }
        if (target.isMesh()) {
            return this.visitMesh(target);
        } else if (target.isWater()) {
            return this.visitWater(target);
        } else if (target.isBatchGroup()) {
            let b = false;
            target.iterate((child)=>{
                if (child !== target) {
                    b ||= this.visit(child);
                }
            });
            return b;
        }
        return false;
    }
    visitWater(node) {
        if (!node.hidden && node.pickable) {
            const bv = node.getWorldBoundingVolume().toAABB();
            const d = this._ray.bboxIntersectionTestEx(bv);
            if (this.updateVisitResult(d, node)) {
                this._intersectedDist = d;
                this._intersected = node.getPickTarget();
                return true;
            }
        }
        return false;
    }
    visitMesh(node) {
        if (!node.hidden && node.pickable) {
            this._ray.transform(node.invWorldMatrix, this._rayLocal);
            const d = node.primitive.raycast(this._rayLocal);
            if (this.updateVisitResult(d, node)) {
                this._intersected = node.getPickTarget();
                return true;
            }
        }
        return false;
    }
    visitOctreeNode(node) {
        if (node.getLevel() === 0 || this._ray.bboxIntersectionTest(node.getBoxLoosed()) !== null) {
            const nodes = node.getNodes();
            for(let i = 0; i < nodes.length; i++){
                this.visit(nodes[i]);
            }
            return true;
        }
        return false;
    }
    updateVisitResult(d, node) {
        if (d !== null) {
            Vector3.combine(this._rayLocal.origin, this._rayLocal.direction, 1, d, tmpV3);
            node.worldMatrix.transformPointAffine(tmpV3, tmpV3);
            const dist = Vector3.distance(tmpV3, this._ray.origin);
            if (dist < this._intersectedDist) {
                this._intersectedDist = dist;
                this._intersectedPoint.set(tmpV3);
                return true;
            }
        }
        return false;
    }
}

export { RaycastVisitor };
//# sourceMappingURL=raycast_visitor.js.map
