import { applyMixins, Disposable, DRef, nextPowerOf2, Vector2 } from '@zephyr3d/base';
import { Primitive } from '../../render/primitive.js';
import { GrassMaterial } from '../../material/grassmaterial.js';
import { mixinDrawable } from '../../render/drawable_mixin.js';

class GrassClusterBase extends Disposable {
    _terrain;
    constructor(terrain){
        super();
        this._terrain = terrain;
    }
    getNode() {
        return this._terrain;
    }
}
class GrassCluster extends applyMixins(GrassClusterBase, mixinDrawable) {
    _primitive;
    _numInstances;
    _material;
    constructor(terrain, baseVertexBuffer, indexBuffer, material, grassData){
        super(terrain);
        this._primitive = new Primitive();
        this._primitive.setVertexBuffer(baseVertexBuffer, 'vertex');
        this._primitive.createAndSetVertexBuffer('tex1_f32x4', grassData, 'instance');
        this._primitive.setIndexBuffer(indexBuffer);
        this._primitive.primitiveType = 'triangle-list';
        this._numInstances = grassData.length >> 2;
        this._material = material;
    }
    getName() {
        return 'GrassCluster';
    }
    getMaterial() {
        return this._material;
    }
    getPrimitive() {
        return this._primitive;
    }
    getPickTarget() {
        return {
            node: this._terrain
        };
    }
    getBoneMatrices() {
        return null;
    }
    getMorphData() {
        return null;
    }
    getMorphInfo() {
        return null;
    }
    getSortDistance(camera) {
        return this._terrain.getSortDistance(camera);
    }
    getQueueType() {
        return this._material.getQueueType();
    }
    isUnlit() {
        return !this._material.supportLighting();
    }
    needSceneColor() {
        return false;
    }
    needSceneDepth() {
        return false;
    }
    isBatchable() {
        return false;
    }
    draw(ctx) {
        this.bind(ctx);
        this._material.draw(this._primitive, ctx, this._numInstances);
    }
    onDispose() {
        super.onDispose();
        this._primitive.dispose();
    }
}
class GrassManager extends Disposable {
    _clusterSize;
    _baseVertexBuffer;
    _indexBuffer;
    _layers;
    constructor(clusterSize, _density){
        super();
        this._clusterSize = clusterSize;
        this._baseVertexBuffer = new Map();
        this._indexBuffer = new DRef();
        this._layers = [];
    }
    onDispose() {
        super.onDispose();
        this._indexBuffer?.dispose();
        this._indexBuffer = null;
        for (const entry of this._baseVertexBuffer){
            entry[1].dispose();
        }
        this._baseVertexBuffer.clear();
        for (const layer of this._layers){
            layer.material.dispose();
        }
        for (const layer of this._layers){
            layer.material.dispose();
            for (const cluster of layer.clusters){
                cluster[1].dispose();
            }
        }
        this._layers = [];
    }
    getBaseVertexBuffer(device, bladeWidth, bladeHeight) {
        const hash = `${bladeWidth}-${bladeHeight}`;
        let baseVertexBuffer = this._baseVertexBuffer.get(hash)?.get();
        if (baseVertexBuffer) {
            return baseVertexBuffer;
        }
        const r = bladeWidth * 0.5;
        const t = bladeHeight;
        const c = r * Math.cos(Math.PI / 3);
        const s = r * Math.sin(Math.PI / 3);
        const vertices = new Float32Array([
            r,
            0,
            0,
            0,
            1,
            r,
            t,
            0,
            0,
            0,
            -r,
            t,
            0,
            1,
            0,
            -r,
            0,
            0,
            1,
            1,
            c,
            0,
            s,
            0,
            1,
            -c,
            0,
            -s,
            1,
            1,
            -c,
            t,
            -s,
            1,
            0,
            c,
            t,
            s,
            0,
            0,
            -c,
            0,
            s,
            0,
            1,
            c,
            0,
            -s,
            1,
            1,
            c,
            t,
            -s,
            1,
            0,
            -c,
            t,
            s,
            0,
            0
        ]);
        baseVertexBuffer = device.createInterleavedVertexBuffer([
            'position_f32x3',
            'tex0_f32x2'
        ], vertices);
        this._baseVertexBuffer.set(hash, new DRef(baseVertexBuffer));
        return baseVertexBuffer;
    }
    getIndexBuffer(device) {
        if (!this._indexBuffer.get()) {
            this._indexBuffer.set(device.createIndexBuffer(new Uint16Array([
                0,
                1,
                2,
                0,
                2,
                3,
                4,
                5,
                6,
                4,
                6,
                7,
                8,
                9,
                10,
                8,
                10,
                11
            ])));
        }
        return this._indexBuffer.get();
    }
    addGrassLayer(device, terrain, density, bladeWidth, bladeHeigh, offset, grassTexture) {
        const densityHeight = density.length;
        const densityWidth = density[0].length;
        const hfScale = terrain.heightFieldScale;
        this._clusterSize = Math.min(nextPowerOf2(this._clusterSize), terrain.width - 1, terrain.height - 1);
        let layer = null;
        terrain.traverseQuadtree((node)=>{
            const size = node.getPatch().getStep() * (terrain.patchSize - 1);
            if (size === this._clusterSize) {
                const bbox = node.getPatch().getBoundingBox();
                const grassData = [];
                const minX = bbox.minPoint.x;
                const minZ = bbox.minPoint.z;
                let index = 0;
                for(let i = 0; i < this._clusterSize; i++){
                    for(let j = 0; j < this._clusterSize; j++){
                        const x0 = minX + j * hfScale.x;
                        const z0 = minZ + i * hfScale.z;
                        const x1 = x0 + hfScale.x;
                        const z1 = z0 + hfScale.z;
                        const u = x0 / terrain.scaledWidth;
                        const v = z0 / terrain.scaledHeight;
                        const du = densityWidth * u >> 0;
                        const dv = densityHeight * v >> 0;
                        const val = density[dv][du] * hfScale.x * hfScale.z;
                        let grassCount = 0;
                        if (val > 0) {
                            if (val < 1) {
                                grassCount = Math.random() <= val ? 1 : 0;
                            } else {
                                grassCount = val >> 0;
                            }
                        }
                        //const grassCount = (this._density[dv][du] * hfScale.x * hfScale.z) >> 0;
                        for(let k = 0; k < grassCount; k++){
                            const x = Math.random() * (x1 - x0) + x0;
                            const z = Math.random() * (z1 - z0) + z0;
                            const y = terrain.getElevation(x, z);
                            const rot = Math.random() * Math.PI * 2;
                            grassData[index++] = x;
                            grassData[index++] = y + offset;
                            grassData[index++] = z;
                            grassData[index++] = rot;
                        }
                    }
                }
                if (grassData.length > 0) {
                    if (!layer) {
                        layer = {
                            material: new DRef(new GrassMaterial(new Vector2(terrain.scaledWidth, terrain.scaledHeight), terrain.quadtree.normalMap, grassTexture)),
                            clusters: new Map()
                        };
                        this._layers.push(layer);
                    }
                    const cluster = new GrassCluster(terrain, this.getBaseVertexBuffer(device, bladeWidth, bladeHeigh), this.getIndexBuffer(device), layer.material.get(), new Float32Array(grassData));
                    layer.clusters.set(node, cluster);
                    node.grassClusters.push(cluster);
                }
            }
        });
        return layer;
    }
}

export { GrassCluster, GrassClusterBase, GrassManager };
//# sourceMappingURL=grass.js.map
