text: import { PointCloudTreeNode } from './PointCloudTree.js';
  import { PointAttributes, PointAttribute, PointAttributeTypes } from './loader/PointAttributes.js';
  import * as THREE from '../libs/three.js/build/three.module.js';
  
  class U {
    static toVector3(v, offset) {
      return new THREE.Vector3().fromArray(v, offset || 0);
    }
  
    static toBox3(b) {
      return new THREE.Box3(U.toVector3(b), U.toVector3(b, 3));
    }
  
    static findDim(schema, name) {
      var dim = schema.find((dim) => dim.name == name);
      if (!dim) throw new Error('Failed to find ' + name + ' in schema');
      return dim;
    }
  
    static sphereFrom(b) {
      return b.getBoundingSphere(new THREE.Sphere());
    }
  }
  
  export class PointCloudEptGeometry {
    constructor(url, info) {
      let version = info.version;
      let schema = info.schema;
      let bounds = info.bounds;
      let boundsConforming = info.boundsConforming;
  
      let xyz = [U.findDim(schema, 'X'), U.findDim(schema, 'Y'), U.findDim(schema, 'Z')];
      let scale = xyz.map((d) => d.scale || 1);
      let offset = xyz.map((d) => d.offset || 0);
      this.eptScale = U.toVector3(scale);
      this.eptOffset = U.toVector3(offset);
  
      this.url = url;
      this.info = info;
      this.type = 'ept';
  
      this.schema = schema;
      this.span = info.span || info.ticks;
      this.boundingBox = U.toBox3(bounds);
      this.tightBoundingBox = U.toBox3(boundsConforming);
      this.offset = U.toVector3([0, 0, 0]);
      this.boundingSphere = U.sphereFrom(this.boundingBox);
      this.tightBoundingSphere = U.sphereFrom(this.tightBoundingBox);
      this.version = new Potree.Version('1.7');
  
      this.projection = null;
      this.fallbackProjection = null;
  
      if (info.srs && info.srs.horizontal) {
        this.projection = info.srs.authority + ':' + info.srs.horizontal;
      }
  
      if (info.srs.wkt) {
        if (!this.projection) this.projection = info.srs.wkt;
        else this.fallbackProjection = info.srs.wkt;
      }
  
      {
        // TODO [mschuetz]: named projections that proj4 can't handle seem to cause problems.
        // remove them for now
  
        try {
          proj4(this.projection);
        } catch (e) {
          this.projection = null;
        }
      }
  
      {
        const attributes = new PointAttributes();
  
        attributes.add(PointAttribute.POSITION_CARTESIAN);
        attributes.add(new PointAttribute('rgba', PointAttributeTypes.DATA_TYPE_UINT8, 4));
        attributes.add(new PointAttribute('intensity', PointAttributeTypes.DATA_TYPE_UINT16, 1));
        attributes.add(new PointAttribute('classification', PointAttributeTypes.DATA_TYPE_UINT8, 1));
        attributes.add(new PointAttribute('gps-time', PointAttributeTypes.DATA_TYPE_DOUBLE, 1));
        attributes.add(new PointAttribute('returnNumber', PointAttributeTypes.DATA_TYPE_UINT8, 1));
        attributes.add(new PointAttribute('number of returns', PointAttributeTypes.DATA_TYPE_UINT8, 1));
        attributes.add(new PointAttribute('return number', PointAttributeTypes.DATA_TYPE_UINT8, 1));
        attributes.add(new PointAttribute('source id', PointAttributeTypes.DATA_TYPE_UINT16, 1));
  
        this.pointAttributes = attributes;
      }
  
      this.spacing = (this.boundingBox.max.x - this.boundingBox.min.x) / this.span;
  
      let hierarchyType = info.hierarchyType || 'json';
  
      const dataType = info.dataType;
      if (dataType == 'laszip') {
        this.loader = new Potree.EptLaszipLoader();
      } else if (dataType == 'binary') {
        this.loader = new Potree.EptBinaryLoader();
      } else if (dataType == 'zstandard') {
        this.loader = new Potree.EptZstandardLoader();
      } else {
        throw new Error('Could not read data type: ' + dataType);
      }
    }
  }
  
  export class EptKey {
    constructor(ept, b, d, x, y, z) {
      this.ept = ept;
      this.b = b;
      this.d = d;
      this.x = x || 0;
      this.y = y || 0;
      this.z = z || 0;
    }
  
    name() {
      return this.d + '-' + this.x + '-' + this.y + '-' + this.z;
    }
  
    step(a, b, c) {
      let min = this.b.min.clone();
      let max = this.b.max.clone();
      let dst = new THREE.Vector3().subVectors(max, min);
  
      if (a) min.x += dst.x / 2;
      else max.x -= dst.x / 2;
  
      if (b) min.y += dst.y / 2;
      else max.y -= dst.y / 2;
  
      if (c) min.z += dst.z / 2;
      else max.z -= dst.z / 2;
  
      return new Potree.EptKey(this.ept, new THREE.Box3(min, max), this.d + 1, this.x * 2 + a, this.y * 2 + b, this.z * 2 + c);
    }
  
    children() {
      var result = [];
      for (var a = 0; a < 2; ++a) {
        for (var b = 0; b < 2; ++b) {
          for (var c = 0; c < 2; ++c) {
            var add = this.step(a, b, c).name();
            if (!result.includes(add)) result = result.concat(add);
          }
        }
      }
      return result;
    }
  }
  
  export class PointCloudEptGeometryNode extends PointCloudTreeNode {
    constructor(ept, b, d, x, y, z) {
      super();
  
      this.ept = ept;
      this.key = new Potree.EptKey(this.ept, b || this.ept.boundingBox, d || 0, x, y, z);
  
      this.id = PointCloudEptGeometryNode.IDCount++;
      this.geometry = null;
      this.boundingBox = this.key.b;
      this.tightBoundingBox = this.boundingBox;
      this.spacing = this.ept.spacing / Math.pow(2, this.key.d);
      this.boundingSphere = U.sphereFrom(this.boundingBox);
  
      // These are set during hierarchy loading.
      this.hasChildren = false;
      this.children = {};
      this.numPoints = -1;
  
      this.level = this.key.d;
      this.loaded = false;
      this.loading = false;
      this.oneTimeDisposeHandlers = [];
  
      let k = this.key;
      this.name = this.toPotreeName(k.d, k.x, k.y, k.z);
      this.index = parseInt(this.name.charAt(this.name.length - 1));
    }
  
    isGeometryNode() {
      return true;
    }
    getLevel() {
      return this.level;
    }
    isTreeNode() {
      return false;
    }
    isLoaded() {
      return this.loaded;
    }
    getBoundingSphere() {
      return this.boundingSphere;
    }
    getBoundingBox() {
      return this.boundingBox;
    }
    url() {
      return this.ept.url + 'ept-data/' + this.filename();
    }
    getNumPoints() {
      return this.numPoints;
    }
  
    filename() {
      return this.key.name();
    }
  
    getChildren() {
      let children = [];
  
      for (let i = 0; i < 8; i++) {
        if (this.children[i]) {
          children.push(this.children[i]);
        }
      }
  
      return children;
    }
  
    addChild(child) {
      this.children[child.index] = child;
      child.parent = this;
    }
  
    load() {
      if (this.loaded || this.loading) return;
      if (Potree.numNodesLoading >= Potree.maxNodesLoading) return;
  
      this.loading = true;
      ++Potree.numNodesLoading;
  
      if (this.numPoints == -1) this.loadHierarchy();
      this.loadPoints();
    }
  
    loadPoints() {
      this.ept.loader.load(this);
    }
  
    async loadHierarchy() {
      let nodes = {};
      nodes[this.filename()] = this;
      this.hasChildren = false;
  
      let eptHierarchyFile = `${this.ept.url}ept-hierarchy/${this.filename()}.json`;
  
      let response = await fetch(eptHierarchyFile);
      let hier = await response.json();
  
      // Since we want to traverse top-down, and 10 comes
      // lexicographically before 9 (for example), do a deep sort.
      var keys = Object.keys(hier).sort((a, b) => {
        let [da, xa, ya, za] = a.split('-').map((n) => parseInt(n, 10));
        let [db, xb, yb, zb] = b.split('-').map((n) => parseInt(n, 10));
        if (da < db) return -1;
        if (da > db) return 1;
        if (xa < xb) return -1;
        if (xa > xb) return 1;
        if (ya < yb) return -1;
        if (ya > yb) return 1;
        if (za < zb) return -1;
        if (za > zb) return 1;
        return 0;
      });
  
      keys.forEach((v) => {
        let [d, x, y, z] = v.split('-').map((n) => parseInt(n, 10));
        let a = x & 1,
          b = y & 1,
          c = z & 1;
        let parentName = d - 1 + '-' + (x >> 1) + '-' + (y >> 1) + '-' + (z >> 1);
  
        let parentNode = nodes[parentName];
        if (!parentNode) return;
        parentNode.hasChildren = true;
  
        let key = parentNode.key.step(a, b, c);
  
        let node = new Potree.PointCloudEptGeometryNode(this.ept, key.b, key.d, key.x, key.y, key.z);
  
        node.level = d;
        node.numPoints = hier[v];
  
        parentNode.addChild(node);
        nodes[key.name()] = node;
      });
    }
  
    doneLoading(bufferGeometry, tightBoundingBox, np, mean) {
      bufferGeometry.boundingBox = this.boundingBox;
      this.geometry = bufferGeometry;
      this.tightBoundingBox = tightBoundingBox;
      this.numPoints = np;
      this.mean = mean;
      this.loaded = true;
      this.loading = false;
      --Potree.numNodesLoading;
    }
  
    toPotreeName(d, x, y, z) {
      var name = 'r';
  
      for (var i = 0; i < d; ++i) {
        var shift = d - i - 1;
        var mask = 1 << shift;
        var step = 0;
  
        if (x & mask) step += 4;
        if (y & mask) step += 2;
        if (z & mask) step += 1;
  
        name += step;
      }
  
      return name;
    }
  
    dispose() {
      if (this.geometry && this.parent != null) {
        this.geometry.dispose();
        this.geometry = null;
        this.loaded = false;
  
        // this.dispatchEvent( { type: 'dispose' } );
        for (let i = 0; i < this.oneTimeDisposeHandlers.length; i++) {
          let handler = this.oneTimeDisposeHandlers[i];
          handler();
        }
        this.oneTimeDisposeHandlers = [];
      }
    }
  }
  
  PointCloudEptGeometryNode.IDCount = 0;
  