import { DRef, Vector3, Vector4 } from '@zephyr3d/base';
import { Quadtree } from './quadtree.js';
import { GraphNode } from '../graph_node.js';
import { GrassManager } from './grass.js';
import { TerrainMaterial } from '../../material/terrainmaterial.js';
import { getDevice } from '../../app/api.js';

/**
 * Terrain node
 * @public
 */ class Terrain extends GraphNode {
    /** @internal */ _quadtree;
    /** @internal */ _grassManager;
    /** @internal */ _maxPixelError;
    /** @internal */ _maxPixelErrorDirty;
    /** @internal */ _lodCamera;
    /** @internal */ _heightFieldScale;
    /** @internal */ _patchSize;
    /** @internal */ _lastTanHalfFOVY;
    /** @internal */ _width;
    /** @internal */ _height;
    /** @internal */ _material;
    /** @internal */ _viewPoint;
    /** @internal */ _castShadow;
    /**
   * Creates an instance of Terrain
   * @param scene - The scene to which the terrain belongs
   */ constructor(scene){
        super(scene);
        this._quadtree = new DRef();
        this._grassManager = new DRef();
        this._maxPixelError = 10;
        this._maxPixelErrorDirty = true;
        this._lodCamera = null;
        this._heightFieldScale = Vector3.one();
        this._patchSize = 33;
        this._lastTanHalfFOVY = 0;
        this._width = 0;
        this._height = 0;
        this._material = null;
        this._viewPoint = null;
        this._castShadow = true;
    }
    async clone() {
        console.error('Cloning terrain not implemented');
        return null;
    }
    /** @internal */ get quadtree() {
        return this._quadtree.get();
    }
    /**
   * {@inheritDoc Drawable.getName}
   */ getName() {
        return this._name;
    }
    /** Wether the mesh node casts shadows */ get castShadow() {
        return this._castShadow;
    }
    set castShadow(val) {
        this._castShadow = !!val;
    }
    /** The maximum pixel error for terrain LOD */ get maxPixelError() {
        return this._maxPixelError;
    }
    set maxPixelError(val) {
        if (val !== this._maxPixelError) {
            this._maxPixelError = val;
            this._maxPixelErrorDirty = true;
        }
    }
    /** Camera that will be used to compute LOD level of terrain patches */ get LODCamera() {
        return this._lodCamera;
    }
    set LODCamera(camera) {
        this._lodCamera = camera;
    }
    /** Scaled terrain width */ get scaledWidth() {
        return this._width * this._heightFieldScale.x;
    }
    /** Scaled terrain height */ get scaledHeight() {
        return this._height * this._heightFieldScale.z;
    }
    /** Scale value of the height field */ get heightFieldScale() {
        return this._heightFieldScale;
    }
    /** @internal */ get patchSize() {
        return this._patchSize;
    }
    /** Width of the terrain */ get width() {
        return this._width;
    }
    /** Height of the terrain */ get height() {
        return this._height;
    }
    /** Material of the terrain */ get material() {
        return this._material;
    }
    /** Normal map of the terrain */ get normalMap() {
        return this._quadtree.get()?.normalMap ?? null;
    }
    /**
   * Creates the terrain
   *
   * @param sizeX - Terrain size in X axis
   * @param sizeZ - Terrain size in Z axis
   * @param elevations - Elevation data of the terrain
   * @param scale - Scale of the terrain
   * @param patchSize - Patch size of the terrain
   * @returns true if succeeded
   */ create(sizeX, sizeZ, elevations, scale, patchSize, options) {
        this._quadtree.set(new Quadtree(this));
        if (options?.splatMap && options.splatMap.format !== 'rgba8unorm') {
            throw new Error('SplatMap must be rgba8unorm format');
        }
        this._material = new TerrainMaterial(options);
        if (!this._quadtree.get().build(patchSize, sizeX, sizeZ, elevations, scale, 24)) {
            this._quadtree.dispose();
            return false;
        }
        this._patchSize = patchSize;
        this._heightFieldScale.set(scale);
        this._width = sizeX;
        this._height = sizeZ;
        this._material.normalTexture = this._quadtree.get().normalMap;
        this._material.normalTexCoordIndex = -1;
        this._material.terrainInfo = new Vector4(this.scaledWidth, this.scaledHeight, 0, 0);
        this.invalidateBoundingVolume();
        // create grass layers
        if (options?.splatMap && options?.detailMaps?.grass) {
            if (options.detailMaps.grass.findIndex((a)=>a && a.findIndex((b)=>!!b) >= 0) >= 0) {
                const splatMap = options.splatMap;
                const data = new Uint8Array(splatMap.width * splatMap.height * 4);
                splatMap.readPixels(0, 0, splatMap.width, splatMap.height, 0, 0, data).then(()=>{
                    for(let detail = 0; detail < 4; detail++){
                        if (options.detailMaps.grass[detail]) {
                            for (const grass of options.detailMaps.grass[detail]){
                                if (grass) {
                                    const d = grass.density ?? 1;
                                    const bladeWidth = grass.bladeWidth ?? 4;
                                    const bladeHeight = grass.bladeHeigh ?? 2;
                                    const offset = grass.offset ?? 0;
                                    const grassTexture = grass.texture ?? null;
                                    const density = [];
                                    for(let i = 0; i < splatMap.height; i++){
                                        const row = [];
                                        for(let j = 0; j < splatMap.width; j++){
                                            const val = data[i * 4 * splatMap.width + j * 4 + detail] / 255;
                                            row.push(val * d);
                                        }
                                        density.push(row);
                                    }
                                    this.createGrass(density, bladeWidth, bladeHeight, offset, grassTexture);
                                }
                            }
                        }
                    }
                });
            }
        }
        return true;
    }
    /**
   * Create grass fields
   * @param maxGrassPerCell - Maximum number of grasses in a cell (world space 1x1)
   * @param density - The density map
   */ createGrass(density, bladeWidth, bladeHeight, offset, grassTexture) {
        if (!this._grassManager.get()) {
            this._grassManager.set(new GrassManager(64, density));
        }
        this._grassManager.get().addGrassLayer(getDevice(), this, density, bladeWidth, bladeHeight, offset, grassTexture);
    }
    /** Get elevation at specified position in terrain coordinate space */ getElevation(x, z) {
        return this._quadtree.get().getHeightField().getRealHeight(x, z);
    }
    /** Get normal at specified position in terrain coordinate space */ getNormal(x, z, normal) {
        return this._quadtree.get().getHeightField().getRealNormal(x, z, normal);
    }
    /** Get intersection distance by a ray in terrain coordinate space */ rayIntersect(ray) {
        return this._quadtree.get().getHeightField().rayIntersect(ray);
    }
    /**
   * {@inheritDoc SceneNode.computeBoundingVolume}
   * @override
   */ computeBoundingVolume() {
        return this._quadtree.get()?.getHeightField().getBoundingbox();
    }
    /**
   * Traverse quadtree node top down
   * @param callback - the callback function
   */ traverseQuadtree(callback) {
        function visitQuadtreeNode_r(node) {
            callback(node);
            for(let i = 0; i < 4; i++){
                const child = node.getChild(i);
                if (child) {
                    visitQuadtreeNode_r(child);
                }
            }
        }
        const rootNode = this._quadtree.get().rootNode;
        if (rootNode) {
            visitQuadtreeNode_r(this._quadtree.get().rootNode);
        }
    }
    /** @internal */ cull(cullVisitor) {
        const tanHalfFovy = cullVisitor.primaryCamera.getTanHalfFovy();
        if (tanHalfFovy !== this._lastTanHalfFOVY || this._maxPixelErrorDirty) {
            this._maxPixelErrorDirty = false;
            this._lastTanHalfFOVY = tanHalfFovy;
            this._quadtree.get().setupCamera(1024, tanHalfFovy, this._maxPixelError);
        }
        const worldEyePos = cullVisitor.primaryCamera.getWorldPosition();
        this._viewPoint = this.invWorldMatrix.transformPointAffine(worldEyePos);
        return this._quadtree.get().cull(cullVisitor, this._viewPoint, this.worldMatrix);
    }
    /**
   * {@inheritDoc SceneNode.isTerrain}
   * @override
   */ isTerrain() {
        return true;
    }
    onDispose() {
        super.onDispose();
        this._grassManager.dispose();
        this._material?.dispose();
        this._quadtree.dispose();
    }
}

export { Terrain };
//# sourceMappingURL=terrain.js.map
