import { ClipState } from '@zephyr3d/base';
import { OctreeNode } from '../scene/octree.js';
import { RENDER_PASS_TYPE_OBJECT_COLOR, RENDER_PASS_TYPE_SHADOWMAP } from '../values.js';

/**
 * Node visitor for culling
 * @public
 */ class CullVisitor {
    /** @internal */ _camera;
    /** @internal */ _skipClipTest;
    /** @internal */ _renderQueue;
    /** @internal */ _renderPass;
    /** @internal */ _isGPUPicking;
    /** @internal */ _isShadowMapping;
    /**
   * Creates an instance of CullVisitor
   * @param renderPass - Render pass for the culling task
   * @param camera - Camera that will be used for culling
   * @param rendeQueue - RenderQueue
   * @param viewPoint - Camera position of the primary render pass
   */ constructor(renderPass, camera, renderQueue){
        this._camera = camera;
        this._renderQueue = renderQueue;
        this._skipClipTest = false;
        this._renderPass = renderPass;
        this._isGPUPicking = this._renderPass.type === RENDER_PASS_TYPE_OBJECT_COLOR;
        this._isShadowMapping = this._renderPass.type === RENDER_PASS_TYPE_SHADOWMAP;
    }
    /** The camera that will be used for culling */ get camera() {
        return this._camera;
    }
    set camera(camera) {
        this._camera = camera || null;
    }
    /** true if cull with frustum culling, otherwise false. default is true */ get frustumCulling() {
        return !this._skipClipTest;
    }
    set frustumCulling(val) {
        this._skipClipTest = !val;
    }
    /** Render pass for the culling task */ get renderPass() {
        return this._renderPass;
    }
    /** The result of culling */ get renderQueue() {
        return this._renderQueue;
    }
    set renderQueue(renderQueue) {
        this._renderQueue = renderQueue;
    }
    /** Frustum for culling */ get frustum() {
        return this._camera?.frustum || null;
    }
    /** @internal */ push(camera, drawable) {
        this.renderQueue.push(camera, drawable);
    }
    /** @internal */ pushRenderQueue(renderQueue) {
        this.renderQueue.pushRenderQueue(renderQueue);
    }
    /**
   * Visits a node
   * @param target - The node to be visit
   */ visit(target) {
        if (target instanceof OctreeNode) {
            return this.visitOctreeNode(target);
        } else if (target.isMesh()) {
            return this.visitMesh(target);
        } else if (target.isSprite()) {
            return this.visitSprite(target);
        } else if (target.isWater()) {
            return this.visitWater(target);
        } else if (target.isParticleSystem()) {
            return this.visitParticleSystem(target);
        } else if (target.isClipmapTerrain()) {
            return this.visitClipmapTerrain(target);
        } else if (target.isPunctualLight()) {
            return this.visitPunctualLight(target);
        } else if (target.isBatchGroup()) {
            return this.visitBatchGroup(target);
        } else {
            return false;
        }
    }
    /** @internal */ visitPunctualLight(node) {
        if (!node.hidden) {
            const clipState = this.getClipStateWithNode(node);
            if (clipState !== ClipState.NOT_CLIPPED) {
                this.renderQueue.pushLight(node);
                return true;
            }
        }
        return false;
    }
    /** @internal */ visitClipmapTerrain(node) {
        if (!node.hidden && (node.castShadow || !this._isShadowMapping) && (node.gpuPickable || !this._isGPUPicking)) {
            const clipState = this.getClipStateWithNode(node);
            if (clipState !== ClipState.NOT_CLIPPED) {
                this.push(this._camera, node);
                return true;
            }
        }
        return false;
    }
    /** @internal */ visitBatchGroup(node) {
        if (!node.hidden) {
            const clipState = this.getClipStateWithNode(node);
            if (clipState !== ClipState.NOT_CLIPPED) {
                node.cull(this);
                return true;
            }
        }
        return false;
    }
    /** @internal */ visitParticleSystem(node) {
        if (!node.hidden && !this._isShadowMapping && (node.gpuPickable || !this._isGPUPicking)) {
            const clipState = this.getClipStateWithNode(node);
            if (clipState !== ClipState.NOT_CLIPPED) {
                this.push(this._camera, node);
                return true;
            }
        }
        return false;
    }
    visitSprite(node) {
        if (!node.hidden && !this._isShadowMapping && (node.gpuPickable || !this._isGPUPicking)) {
            const clipState = this.getClipStateWithNode(node);
            if (clipState !== ClipState.NOT_CLIPPED) {
                this.push(this._camera, node);
                return true;
            }
        }
        return false;
    }
    /** @internal */ visitMesh(node) {
        if (!node.hidden && (node.castShadow || !this._isShadowMapping) && (node.gpuPickable || !this._isGPUPicking)) {
            const clipState = this.getClipStateWithNode(node);
            if (clipState !== ClipState.NOT_CLIPPED) {
                this.push(this._camera, node);
                return true;
            }
        }
        return false;
    }
    /** @internal */ visitWater(node) {
        if (!node.hidden && !this._isShadowMapping && (node.gpuPickable || !this._isGPUPicking)) {
            const clipState = this.getClipStateWithNode(node);
            if (clipState !== ClipState.NOT_CLIPPED) {
                this.push(this._camera, node);
                return true;
            }
        }
        return false;
    }
    /** @internal */ visitOctreeNode(node) {
        const clipState = node.getLevel() > 0 ? this.getClipStateWithAABB(node.getBoxLoosed()) : ClipState.CLIPPED;
        if (clipState !== ClipState.NOT_CLIPPED) {
            const saveSkipFlag = this._skipClipTest;
            this._skipClipTest = this._skipClipTest || clipState === ClipState.A_INSIDE_B;
            const nodes = node.getNodes();
            for(let i = 0; i < nodes.length; i++){
                this.visit(nodes[i]);
            }
            this._skipClipTest = saveSkipFlag;
            return true;
        }
        return false;
    }
    /** @internal */ getClipStateWithNode(node) {
        let clipState;
        if (this._skipClipTest) {
            clipState = ClipState.A_INSIDE_B;
        } else if (!node.clipTestEnabled) {
            clipState = ClipState.CLIPPED;
        } else {
            const bv = node.getWorldBoundingVolume();
            clipState = bv ? this.getClipStateWithAABB(bv.toAABB()) : ClipState.CLIPPED;
        }
        return clipState;
    }
    /** @internal */ getClipStateWithAABB(aabb) {
        return this.camera.clipMask ? aabb.getClipStateWithFrustumMask(this.frustum, this.camera.clipMask) : aabb.getClipStateWithFrustum(this.frustum);
    }
}

export { CullVisitor };
//# sourceMappingURL=cull_visitor.js.map
