text: "use strict";
  
  import { Extent } from "../Extent.js";
  import { LonLat } from "../LonLat.js";
  import { MAX, MIN } from "../math.js";
  import { Vec3 } from "../math/Vec3.js";
  import { MAX_LAT } from "../mercator.js";
  import { EPSG3857 } from "../proj/EPSG3857.js";
  import { EPSG4326 } from "../proj/EPSG4326.js";
  import { getMatrixSubArray, getMatrixSubArrayBoundsExt } from "../utils/shared.js";
  import {
      COMSIDE,
      E,
      MAX_RENDERED_NODES,
      N,
      NE,
      NEIGHBOUR,
      NOTRENDERING,
      NW,
      OPPART,
      OPSIDE,
      PARTOFFSET,
      RENDERING,
      S,
      SE,
      SW,
      VISIBLE_DISTANCE,
      W,
      WALKTHROUGH
  } from "./quadTree.js";
  
  let _tempHigh = new Vec3(),
      _tempLow = new Vec3();
  
  const _vertOrder = [
      { x: 0, y: 0 },
      { x: 1, y: 0 },
      { x: 0, y: 1 },
      { x: 1, y: 1 }
  ];
  const _neGridSize = Math.sqrt(_vertOrder.length) - 1;
  
  let BOUNDS = {
      xmin: 0.0,
      ymin: 0.0,
      zmin: 0.0,
      xmax: 0.0,
      ymax: 0.0,
      zmax: 0.0
  };
  
  /**
   * Quad tree planet segment node.
   * @constructor
   * @param {Segment|og.planetSegment.SegmentLonLat} segmentPrototype - Planet segment node constructor.
   * @param {RenderNode} planet - Planet render node.
   * @param {number} partId - NorthEast, SouthWest etc.
   * @param {quadTree.Node} parent - Parent of this node.
   * @param {number} id - Tree node identifier (id * 4 + 1);
   * @param {number} tileZoom - Deep index of the quad tree.
   * @param {Extent} extent - Planet segment extent.
   */
  class Node {
  
      constructor(SegmentPrototype, planet, partId, parent, id, tileZoom, extent) {
          this.SegmentPrototype = SegmentPrototype;
          planet._createdNodesCount++;
          this.planet = planet;
          this.parentNode = parent;
          this.partId = partId;
          this.nodeId = partId + id;
          this.state = null;
          this.appliedTerrainNodeId = -1;
          this.sideSize = [1, 1, 1, 1];
          this.sideSizeLog2 = [0, 0, 0, 0];
          this.ready = false;
          this.neighbors = [[], [], [], []];
          this.equalizedNeighborId = [-1, -1, -1, -1];
          this.equalizedNeighborGridSize = [-1, -1, -1, -1];
          this.nodes = [null, null, null, null];
          this.segment = new SegmentPrototype(this, planet, tileZoom, extent);
          this._cameraInside = false;
          this.inFrustum = 0;
          this.createBounds();
      }
  
      createChildrenNodes() {
          this.ready = true;
  
          var p = this.planet;
          var ps = this.segment;
          var ext = ps._extent;
          var size_x = ext.getWidth() * 0.5;
          var size_y = ext.getHeight() * 0.5;
          var ne = ext.northEast,
              sw = ext.southWest;
          var z = ps.tileZoom + 1;
          var id = this.nodeId * 4 + 1;
          var c = new LonLat(sw.lon + size_x, sw.lat + size_y);
          var nd = this.nodes;
  
          nd[NW] = new Node(this.SegmentPrototype, p, NW, this, id, z, new Extent(new LonLat(sw.lon, sw.lat + size_y), new LonLat(sw.lon + size_x, ne.lat)));
          nd[NE] = new Node(this.SegmentPrototype, p, NE, this, id, z, new Extent(c, new LonLat(ne.lon, ne.lat)));
          nd[SW] = new Node(this.SegmentPrototype, p, SW, this, id, z, new Extent(new LonLat(sw.lon, sw.lat), c));
          nd[SE] = new Node(this.SegmentPrototype, p, SE, this, id, z, new Extent(new LonLat(sw.lon + size_x, sw.lat), new LonLat(ne.lon, sw.lat + size_y)));
      }
  
      createBounds() {
          let seg = this.segment;
          seg._setExtentLonLat();
          if (seg.tileZoom === 0) {
              seg.setBoundingSphere(0.0, 0.0, 0.0, new Vec3(0.0, 0.0, seg.planet.ellipsoid._a));
          } else if (seg.tileZoom < seg.planet.terrain.minZoom) {
              seg.createBoundsByExtent();
          } else {
              seg.createBoundsByParent();
          }
  
          let x = seg.bsphere.center.x,
              y = seg.bsphere.center.y,
              z = seg.bsphere.center.z;
  
          let length = 1.0 / Math.sqrt(x * x + y * y + z * z);
          seg.centerNormal.x = x * length;
          seg.centerNormal.y = y * length;
          seg.centerNormal.z = z * length;
      }
  
      getState() {
          if (this.state === -1) {
              return this.state;
          }
          var pn = this.parentNode;
          while (pn) {
              if (pn.state !== WALKTHROUGH) {
                  return NOTRENDERING;
              }
              pn = pn.parentNode;
          }
          return this.state;
      }
  
      /**
       * Returns the same deep existent neighbour node.
       * @public
       * @param {Number} side - Neighbour side index e.g. og.quadTree.N, og.quadTree.W etc.
       * @returns {quadTree.Node} -
       */
      getEqualNeighbor(side) {
          var pn = this;
          var part = NEIGHBOUR[side][pn.partId];
          if (part !== -1) {
              return pn.parentNode.nodes[part];
          } else {
              var pathId = [];
              while (pn.parentNode) {
                  pathId.push(pn.partId);
                  part = NEIGHBOUR[side][pn.partId];
                  pn = pn.parentNode;
                  if (part !== -1) {
                      var i = pathId.length;
                      side = OPSIDE[side];
                      while (pn && i--) {
                          part = OPPART[side][pathId[i]];
                          pn = pn.nodes[part];
                      }
                      return pn;
                  }
              }
          }
      }
  
      isBrother(node) {
          return !(this.parentNode || node.parentNode) || this.parentNode.id === node.parentNode.id;
      }
  
      renderTree(cam, maxZoom, terrainReadySegment, stopLoading) {
          if (this.planet._renderedNodes.length >= MAX_RENDERED_NODES) {
              return;
          }
  
          this.state = WALKTHROUGH;
  
          this.neighbors[0] = null;
          this.neighbors[1] = null;
          this.neighbors[2] = null;
          this.neighbors[3] = null;
  
          this.neighbors[0] = [];
          this.neighbors[1] = [];
          this.neighbors[2] = [];
          this.neighbors[3] = [];
  
          let seg = this.segment,
              planet = this.planet;
  
          this._cameraInside = false;
  
          let insideLonLat = null;
  
          // Search a node which the camera is flying over.
          if (!this.parentNode || this.parentNode._cameraInside) {
              let inside;
              if (Math.abs(cam._lonLat.lat) <= MAX_LAT && seg._projection.id === EPSG3857.id) {
                  inside = seg._extent.isInside(cam._lonLatMerc);
                  insideLonLat = cam._lonLatMerc;
              } else if (seg._projection.id === EPSG4326.id) {
                  inside = seg._extent.isInside(cam._lonLat);
                  insideLonLat = cam._lonLat;
              }
  
              if (inside) {
                  cam._insideSegmentPosition.lon = insideLonLat.lon;
                  cam._insideSegmentPosition.lat = insideLonLat.lat;
                  cam._insideSegment = seg;
                  this._cameraInside = true;
              }
          }
  
          this.inFrustum = 0;
  
          let frustums = cam.frustums,
              numFrustums = frustums.length;
  
          if (seg.tileZoom < 6) {
              for (let i = 0; i < numFrustums; i++) {
                  if (frustums[i].containsSphere(seg.bsphere)) {
                      this.inFrustum |= 1 << i;
                  }
              }
          } else {
              let commonFrustumFlag = 1 << (numFrustums - 1 - 1);
              for (let i = 0; commonFrustumFlag && i < numFrustums; i++) {
                  if (seg.terrainReady) {
                      if (frustums[i].containsBox(seg.bbox)) {
                          commonFrustumFlag >>= 1;
                          this.inFrustum |= 1 << i;
                      }
                  } else {
                      if (frustums[i].containsSphere(seg.bsphere)) {
                          commonFrustumFlag >>= 1;
                          this.inFrustum |= 1 << i;
                      }
                  }
              }
          }
  
          if (this.inFrustum || this._cameraInside || seg.tileZoom < 3) {
              let h = cam._lonLat.height;
  
              let eye = cam.eye;
              let horizonDist = eye.length2() - this.planet.ellipsoid._b2;
  
              let altVis = seg.tileZoom > 19 ||
                  (seg.tileZoom < 4 && !seg.terrainReady) ||
                  seg.tileZoom < 2;
  
              if (h > 21000) {
                  altVis = altVis || eye.distance2(seg._sw) < horizonDist
                      || eye.distance2(seg._nw) < horizonDist
                      || eye.distance2(seg._ne) < horizonDist
                      || eye.distance2(seg._se) < horizonDist;
              } else {
                  altVis = altVis || cam.eye.distance(seg.bsphere.center) - seg.bsphere.radius < VISIBLE_DISTANCE * Math.sqrt(h);
              }
  
              if ((this.inFrustum && (altVis || h > 10000.0)) || this._cameraInside) {
                  seg._collectVisibleNodes();
              }
  
              if (seg.tileZoom < 2 && seg.normalMapReady) {
                  this.traverseNodes(cam, maxZoom, terrainReadySegment, stopLoading);
              } else if (seg.terrainReady && (
                  !maxZoom && cam.projectedSize(seg.bsphere.center, seg._plainRadius) < planet._lodSize ||
                  maxZoom && ((seg.tileZoom === maxZoom) || !altVis))) {
  
                  if (altVis) {
                      seg.passReady = true;
                      this.renderNode(this.inFrustum, !this.inFrustum, terrainReadySegment, stopLoading);
                  } else {
                      this.state = NOTRENDERING;
                  }
  
              } else if (
                  seg.terrainReady &&
                  seg.tileZoom < planet.terrain._maxNodeZoom &&
                  (!maxZoom || maxZoom && cam.projectedSize(seg.bsphere.center, seg.bsphere.radius) > this.planet._maxLodSize)) {
                  this.traverseNodes(cam, maxZoom, seg, stopLoading);
              } else if (altVis) {
                  seg.passReady = maxZoom ? seg.terrainReady : false;
                  this.renderNode(this.inFrustum, !this.inFrustum, terrainReadySegment, stopLoading);
              } else {
                  this.state = NOTRENDERING;
              }
          } else {
              this.state = NOTRENDERING;
          }
      }
  
      traverseNodes(cam, maxZoom, terrainReadySegment, stopLoading) {
          if (!this.ready) {
              this.createChildrenNodes();
          }
  
          let n = this.nodes;
  
          n[0].renderTree(cam, maxZoom, terrainReadySegment, stopLoading);
          n[1].renderTree(cam, maxZoom, terrainReadySegment, stopLoading);
          n[2].renderTree(cam, maxZoom, terrainReadySegment, stopLoading);
          n[3].renderTree(cam, maxZoom, terrainReadySegment, stopLoading);
      }
  
      renderNode(inFrustum, onlyTerrain, terrainReadySegment, stopLoading) {
          var seg = this.segment;
  
          // Create and load terrain data
          if (!seg.terrainReady) {
              if (!seg.initialized) {
                  seg.initialize();
              }
  
              this.whileTerrainLoading(terrainReadySegment, stopLoading);
  
              if (!seg.plainProcessing) {
                  seg.createPlainSegmentAsync();
              }
  
              if (seg.plainReady && !stopLoading) {
                  seg.loadTerrain();
              }
          }
  
          // Create normal map texture
          if (seg.planet.lightEnabled && !seg.normalMapReady /*&& !seg.parentNormalMapReady*/) {
              this.whileNormalMapCreating();
          }
  
          if (onlyTerrain) {
              this.state = -1;
              return;
          }
  
          // Calculate minimal and maximal zoom index on the screen
          if (!this._cameraInside && seg.tileZoom > this.planet.maxCurrZoom) {
              this.planet.maxCurrZoom = seg.tileZoom;
          }
  
          if (seg.tileZoom < this.planet.minCurrZoom) {
              this.planet.minCurrZoom = seg.tileZoom;
          }
  
          seg._addViewExtent();
  
          // Finally this node proceeds to rendering.
          this.addToRender(inFrustum);
      }
  
      /**
       * Seraching for neighbours and pickup current node to render processing.
       * @public
       */
      addToRender(inFrustum) {
          this.state = RENDERING;
  
          var nodes = this.planet._renderedNodes;
  
          for (var i = nodes.length - 1; i >= 0; --i) {
              var ni = nodes[i];
  
              var cs = this.getCommonSide(ni);
  
              if (cs !== -1) {
                  var opcs = OPSIDE[cs];
  
                  if (this.neighbors[cs].length === 0 || ni.neighbors[opcs].length === 0) {
                      var ap = this.segment;
                      var bp = ni.segment;
                      var ld = ap.gridSize / (bp.gridSize * Math.pow(2, bp.tileZoom - ap.tileZoom));
  
                      let cs_size = ap.gridSize,
                          opcs_size = bp.gridSize;
  
                      if (ld > 1) {
                          cs_size = Math.ceil(ap.gridSize / ld);
                          opcs_size = bp.gridSize;
                      } else if (ld < 1) {
                          cs_size = ap.gridSize;
                          opcs_size = Math.ceil(bp.gridSize * ld);
                      }
  
                      this.sideSize[cs] = cs_size;
                      ni.sideSize[opcs] = opcs_size;
  
                      this.sideSizeLog2[cs] = Math.log2(cs_size);
                      ni.sideSizeLog2[opcs] = Math.log2(opcs_size);
                  }
  
                  this.neighbors[cs].push(ni);
                  ni.neighbors[opcs].push(this);
              }
          }
  
          nodes.push(this);
  
          if (!this.segment.terrainReady) {
              this.planet._renderCompleted = false;
          }
  
          let k = 0,
              rf = this.planet._renderedNodesInFrustum;
          while (inFrustum) {
              if (inFrustum & 1) {
                  rf[k].push(this);
              }
              k++;
              inFrustum >>= 1;
          }
      }
  
      getCommonSide(node) {
          var as = this.segment,
              bs = node.segment;
  
          if (as.tileZoom === bs.tileZoom && as._tileGroup === bs._tileGroup) {
              return as.getNeighborSide(bs);
          } else {
              var a = as._extentLonLat,
                  b = bs._extentLonLat;
  
              var a_ne = a.northEast,
                  a_sw = a.southWest,
                  b_ne = b.northEast,
                  b_sw = b.southWest;
  
              var a_ne_lon = a_ne.lon,
                  a_ne_lat = a_ne.lat,
                  a_sw_lon = a_sw.lon,
                  a_sw_lat = a_sw.lat,
                  b_ne_lon = b_ne.lon,
                  b_ne_lat = b_ne.lat,
                  b_sw_lon = b_sw.lon,
                  b_sw_lat = b_sw.lat;
  
              if (as._tileGroup === bs._tileGroup) {
                  if (
                      a_ne_lon === b_sw_lon &&
                      ((a_ne_lat <= b_ne_lat && a_sw_lat >= b_sw_lat) ||
                          (a_ne_lat >= b_ne_lat && a_sw_lat <= b_sw_lat))
                  ) {
                      return E;
                  } else if (
                      a_sw_lon === b_ne_lon &&
                      ((a_ne_lat <= b_ne_lat && a_sw_lat >= b_sw_lat) ||
                          (a_ne_lat >= b_ne_lat && a_sw_lat <= b_sw_lat))
                  ) {
                      return W;
                  } else if (
                      a_ne_lat === b_sw_lat &&
                      ((a_sw_lon >= b_sw_lon && a_ne_lon <= b_ne_lon) ||
                          (a_sw_lon <= b_sw_lon && a_ne_lon >= b_ne_lon))
                  ) {
                      return N;
                  } else if (
                      a_sw_lat === b_ne_lat &&
                      ((a_sw_lon >= b_sw_lon && a_ne_lon <= b_ne_lon) ||
                          (a_sw_lon <= b_sw_lon && a_ne_lon >= b_ne_lon))
                  ) {
                      return S;
                  } else if (
                      bs.tileX === 0 &&
                      as.tileX === Math.pow(2, as.tileZoom) - 1 &&
                      ((a_ne_lat <= b_ne_lat && a_sw_lat >= b_sw_lat) ||
                          (a_ne_lat >= b_ne_lat && a_sw_lat <= b_sw_lat))
                  ) {
                      return E;
                  } else if (
                      as.tileX === 0 &&
                      bs.tileX === Math.pow(2, bs.tileZoom) - 1 &&
                      ((a_ne_lat <= b_ne_lat && a_sw_lat >= b_sw_lat) ||
                          (a_ne_lat >= b_ne_lat && a_sw_lat <= b_sw_lat))
                  ) {
                      return W;
                  }
              }
  
              if (
                  as._tileGroup === 0 &&
                  bs._tileGroup === 1 &&
                  as.tileY === 0 &&
                  bs.tileY === Math.pow(2, bs.tileZoom) - 1 &&
                  ((a_sw_lon >= b_sw_lon && a_ne_lon <= b_ne_lon) ||
                      (a_sw_lon <= b_sw_lon && a_ne_lon >= b_ne_lon))
              ) {
                  return N;
              } else if (
                  as._tileGroup === 2 &&
                  bs._tileGroup === 0 &&
                  as.tileY === 0 &&
                  bs.tileY === Math.pow(2, bs.tileZoom) - 1 &&
                  ((a_sw_lon >= b_sw_lon && a_ne_lon <= b_ne_lon) ||
                      (a_sw_lon <= b_sw_lon && a_ne_lon >= b_ne_lon))
              ) {
                  return N;
              } else if (
                  bs._tileGroup === 1 &&
                  as._tileGroup === 0 &&
                  as.tileY === Math.pow(2, as.tileZoom) - 1 &&
                  bs.tileY === 0 &&
                  ((a_sw_lon >= b_sw_lon && a_ne_lon <= b_ne_lon) ||
                      (a_sw_lon <= b_sw_lon && a_ne_lon >= b_ne_lon))
              ) {
                  return S;
              } else if (
                  as._tileGroup === 1 &&
                  bs._tileGroup === 0 &&
                  as.tileY === Math.pow(2, as.tileZoom) - 1 &&
                  bs.tileY === 0 &&
                  ((a_sw_lon >= b_sw_lon && a_ne_lon <= b_ne_lon) ||
                      (a_sw_lon <= b_sw_lon && a_ne_lon >= b_ne_lon))
              ) {
                  return S;
              }
          }
  
          return -1;
      }
  
      // TODO: test test test
      ___getCommonSide___(b) {
          var a = this,
              as = a.segment,
              bs = b.segment;
  
          if (as.tileZoom === bs.tileZoom) {
              return as.getNeighborSide(bs);
          } else if (as.tileZoom > bs.tileZoom) {
              let dz = as.tileZoom - bs.tileZoom,
                  i = dz,
                  p = this;
  
              while (i--) {
                  p = p.parentNode;
              }
  
              let side = p.segment.getNeighborSide(bs);
  
              if (side !== -1) {
                  i = dz;
                  p = this;
                  let _n = true;
  
                  while (i--) {
                      _n = _n && COMSIDE[p.partId][side];
                  }
  
                  if (_n) {
                      return side;
                  }
              }
          } else {
              let dz = bs.tileZoom - as.tileZoom,
                  i = dz,
                  p = b;
  
              while (i--) {
                  p = p.parentNode;
              }
  
              let side = p.segment.getNeighborSide(as);
  
              if (side !== -1) {
                  i = dz;
                  p = b;
                  let _n = true;
  
                  while (i--) {
                      _n = _n && COMSIDE[p.partId][side];
                  }
  
                  if (_n) {
                      return OPSIDE[side];
                  }
              }
          }
  
          return -1;
      }
  
      whileNormalMapCreating() {
          var seg = this.segment;
          var maxZ = this.planet.terrain.maxZoom;
  
          if (seg.tileZoom <= maxZ && !seg.terrainIsLoading && seg.terrainReady && !seg._inTheQueue) {
              seg.planet._normalMapCreator.queue(seg);
          }
  
          var pn = this;
  
          while (pn.parentNode && !pn.segment.normalMapReady) {
              pn = pn.parentNode;
          }
  
          var dZ2 = 2 << (seg.tileZoom - pn.segment.tileZoom - 1);
  
          seg.normalMapTexture = pn.segment.normalMapTexture;
          seg.normalMapTextureBias[0] = seg.tileX - pn.segment.tileX * dZ2;
          seg.normalMapTextureBias[1] = seg.tileY - pn.segment.tileY * dZ2;
          seg.normalMapTextureBias[2] = 1.0 / dZ2;
  
          if (seg.tileZoom > maxZ) {
              if (pn.segment.tileZoom === maxZ) {
                  seg.parentNormalMapReady = true;
              }
          }
      }
  
      whileTerrainLoading(terrainReadySegment, stopLoading) {
          const seg = this.segment;
          const terrain = this.planet.terrain;
  
          let pn = this;
  
          if (terrainReadySegment && terrainReadySegment.terrainReady) {
              pn = terrainReadySegment.node;
          } else {
              while (pn.parentNode && !pn.segment.terrainReady) {
                  pn = pn.parentNode;
              }
          }
  
          if (pn.segment.terrainReady && this.appliedTerrainNodeId !== pn.nodeId) {
              let dZ2 = 2 << (seg.tileZoom - pn.segment.tileZoom - 1),
                  offsetX = seg.tileX - pn.segment.tileX * dZ2,
                  offsetY = seg.tileY - pn.segment.tileY * dZ2;
  
              let pseg = pn.segment;
  
              let tempVertices, tempVerticesHigh, tempVerticesLow, noDataVertices;
  
              this.appliedTerrainNodeId = pn.nodeId;
  
              let gridSize = pn.segment.gridSize / dZ2,
                  gridSizeExt = pn.segment.fileGridSize / dZ2;
  
              BOUNDS.xmin = MAX;
              BOUNDS.xmax = MIN;
              BOUNDS.ymin = MAX;
              BOUNDS.ymax = MIN;
              BOUNDS.zmin = MAX;
              BOUNDS.zmax = MIN;
  
              if (gridSize >= 1) {
                  seg.gridSize = gridSize;
  
                  let len = (gridSize + 1) * (gridSize + 1) * 3;
                  tempVertices = new Float64Array(len);
                  tempVerticesHigh = new Float32Array(len);
                  tempVerticesLow = new Float32Array(len);
  
                  if (pseg.noDataVertices) {
                      noDataVertices = new Uint8Array(len / 3);
                  }
  
                  getMatrixSubArrayBoundsExt(
                      pseg.terrainVertices,
                      pseg.terrainVerticesHigh,
                      pseg.terrainVerticesLow,
                      pseg.noDataVertices,
                      pseg.gridSize,
                      gridSize * offsetY,
                      gridSize * offsetX,
                      gridSize,
                      tempVertices,
                      tempVerticesHigh,
                      tempVerticesLow,
                      BOUNDS,
                      noDataVertices
                  );
              } else if (gridSizeExt >= 1) {
                  seg.gridSize = gridSizeExt;
  
                  let len = (gridSizeExt + 1) * (gridSizeExt + 1) * 3;
                  tempVertices = new Float64Array(len);
                  tempVerticesHigh = new Float32Array(len);
                  tempVerticesLow = new Float32Array(len);
  
                  if (pseg.noDataVertices) {
                      noDataVertices = new Uint8Array(len / 3);
                  }
  
                  getMatrixSubArrayBoundsExt(
                      pseg.normalMapVertices,
                      pseg.normalMapVerticesHigh,
                      pseg.normalMapVerticesLow,
                      pseg.noDataVertices,
                      pn.segment.fileGridSize,
                      gridSizeExt * offsetY,
                      gridSizeExt * offsetX,
                      gridSizeExt,
                      tempVertices,
                      tempVerticesHigh,
                      tempVerticesLow,
                      BOUNDS,
                      noDataVertices
                  );
              } else {
                  seg.gridSize = _neGridSize;
  
                  let i0 = Math.floor(gridSize * offsetY),
                      j0 = Math.floor(gridSize * offsetX);
  
                  let bigOne;
                  if (pseg.gridSize === 1) {
                      bigOne = pseg.terrainVertices;
                  } else {
                      bigOne = getMatrixSubArray(pseg.terrainVertices, pseg.gridSize, i0, j0, 1);
                  }
  
                  let insideSize = 1.0 / gridSize;
  
                  let t_i0 = offsetY - insideSize * i0,
                      t_j0 = offsetX - insideSize * j0;
  
                  let v_lt = new Vec3(bigOne[0], bigOne[1], bigOne[2]),
                      v_rb = new Vec3(bigOne[9], bigOne[10], bigOne[11]);
  
                  let vn = new Vec3(bigOne[3] - bigOne[0], bigOne[4] - bigOne[1], bigOne[5] - bigOne[2]),
                      vw = new Vec3(bigOne[6] - bigOne[0], bigOne[7] - bigOne[1], bigOne[8] - bigOne[2]),
                      ve = new Vec3(
                          bigOne[3] - bigOne[9],
                          bigOne[4] - bigOne[10],
                          bigOne[5] - bigOne[11]
                      ),
                      vs = new Vec3(
                          bigOne[6] - bigOne[9],
                          bigOne[7] - bigOne[10],
                          bigOne[8] - bigOne[11]
                      );
  
                  let coords = new Vec3();
  
                  tempVertices = new Float64Array(3 * _vertOrder.length);
                  tempVerticesHigh = new Float32Array(3 * _vertOrder.length);
                  tempVerticesLow = new Float32Array(3 * _vertOrder.length);
  
                  for (var i = 0; i < _vertOrder.length; i++) {
                      let vi_y = _vertOrder[i].y + t_i0,
                          vi_x = _vertOrder[i].x + t_j0;
  
                      let vi_x_is = vi_x * gridSize,
                          vi_y_is = vi_y * gridSize;
  
                      if (vi_y + vi_x < insideSize) {
                          coords = vn.scaleTo(vi_x_is).addA(vw.scaleTo(vi_y_is)).addA(v_lt);
                      } else {
                          coords = vs
                              .scaleTo(1 - vi_x_is)
                              .addA(ve.scaleTo(1 - vi_y_is))
                              .addA(v_rb);
                      }
  
                      Vec3.doubleToTwoFloats(coords, _tempHigh, _tempLow);
  
                      let i3 = i * 3;
  
                      tempVertices[i3] = coords.x;
                      tempVertices[i3 + 1] = coords.y;
                      tempVertices[i3 + 2] = coords.z;
  
                      tempVerticesHigh[i3] = _tempHigh.x;
                      tempVerticesHigh[i3 + 1] = _tempHigh.y;
                      tempVerticesHigh[i3 + 2] = _tempHigh.z;
  
                      tempVerticesLow[i3] = _tempLow.x;
                      tempVerticesLow[i3 + 1] = _tempLow.y;
                      tempVerticesLow[i3 + 2] = _tempLow.z;
  
                      if (coords.x < BOUNDS.xmin) BOUNDS.xmin = coords.x;
                      if (coords.x > BOUNDS.xmax) BOUNDS.xmax = coords.x;
                      if (coords.y < BOUNDS.ymin) BOUNDS.ymin = coords.y;
                      if (coords.y > BOUNDS.ymax) BOUNDS.ymax = coords.y;
                      if (coords.z < BOUNDS.zmin) BOUNDS.zmin = coords.z;
                      if (coords.z > BOUNDS.zmax) BOUNDS.zmax = coords.z;
                  }
              }
  
              seg.readyToEngage = true;
  
              seg.terrainVertices = tempVertices;
              seg.terrainVerticesHigh = tempVerticesHigh;
              seg.terrainVerticesLow = tempVerticesLow;
  
              seg.tempVertices = tempVertices;
              seg.tempVerticesHigh = tempVerticesHigh;
              seg.tempVerticesLow = tempVerticesLow;
  
              seg.noDataVertices = noDataVertices;
  
              seg.setBoundingVolume(
                  BOUNDS.xmin,
                  BOUNDS.ymin,
                  BOUNDS.zmin,
                  BOUNDS.xmax,
                  BOUNDS.ymax,
                  BOUNDS.zmax
              );
  
              if (seg.tileZoom > terrain.maxZoom) {
                  if (pn.segment.tileZoom >= terrain.maxZoom) {
                      //TODO: find better place for this
                      seg._plainRadius = pn.segment._plainRadius / dZ2;
  
                      seg.terrainReady = true;
                      seg.terrainIsLoading = false;
  
                      seg.terrainVertices = tempVertices;
                      seg.terrainVerticesHigh = tempVerticesHigh;
                      seg.terrainVerticesLow = tempVerticesLow;
  
                      this.appliedTerrainNodeId = this.nodeId;
  
                      if (pn.segment.terrainExists) {
                          seg.terrainExists = true;
                          seg.normalMapVertices = tempVertices;
                          seg.fileGridSize = Math.sqrt(tempVertices.length / 3) - 1;
  
                          let fgs = Math.sqrt(pseg.normalMapNormals.length / 3) - 1,
                              fgsZ = fgs / dZ2;
  
                          if (fgs > 1) {
                              seg.normalMapNormals = getMatrixSubArray(
                                  pseg.normalMapNormals,
                                  fgs,
                                  fgsZ * offsetY,
                                  fgsZ * offsetX,
                                  fgsZ
                              );
                          } else {
                              // TODO: interpolation
                              seg.normalMapNormals = pseg.normalMapNormals;
                          }
                      }
                  } else {
                      pn = this;
                      while (pn.parentNode && pn.segment.tileZoom !== terrain.maxZoom) {
                          pn = pn.parentNode;
                      }
  
                      let pns = pn.segment;
  
                      if (!pns.initialized) {
                          pns.initialize();
                      }
  
                      if (!pns.plainProcessing) {
                          pn.segment.createPlainSegmentAsync();
                      }
  
                      if (pns.plainReady && !stopLoading) {
                          pns.loadTerrain(true);
                      }
                  }
              }
          }
      }
  
      destroy() {
          this.state = NOTRENDERING;
          this.segment.destroySegment();
          var n = this.neighbors;
          n[N] && n[N].neighbors && (n[N].neighbors[S] = []);
          n[E] && n[E].neighbors && (n[E].neighbors[W] = []);
          n[S] && n[S].neighbors && (n[S].neighbors[N] = []);
          n[W] && n[W].neighbors && (n[W].neighbors[E] = []);
          this.neighbors = null;
          this.parentNode = null;
          this.sideSize = null;
          this.sideSizeLog2 = null;
          this.segment = null;
      }
  
      clearTree() {
          var state = this.getState();
  
          if (state === NOTRENDERING) {
              this.destroyBranches();
          } else if (state === RENDERING) {
              this.destroyBranches();
          } else {
              for (var i = 0; i < this.nodes.length; i++) {
                  this.nodes[i] && this.nodes[i].clearTree();
              }
          }
      }
  
      clearBranches() {
          for (let i = 0; i < this.nodes.length; i++) {
              this.nodes[i].clearBranches();
              this.nodes[i].segment.deleteMaterials();
          }
      }
  
      destroyBranches() {
          if (this.ready) {
              var nodesToRemove = [],
                  i;
  
              for (i = 0; i < this.nodes.length; i++) {
                  nodesToRemove[i] = this.nodes[i];
              }
  
              this.ready = false;
              this.nodes.length = 0;
  
              for (i = 0; i < nodesToRemove.length; i++) {
                  nodesToRemove[i].destroyBranches();
                  nodesToRemove[i].destroy();
                  nodesToRemove[i] = null;
              }
  
              nodesToRemove.length = 0;
              nodesToRemove = null;
          }
      }
  
      traverseTree(callback) {
          callback(this);
          if (this.ready) {
              for (var i = 0; i < this.nodes.length; i++) {
                  this.nodes[i].traverseTree(callback);
              }
          }
      }
  
      getOffsetOppositeNeighbourSide(neighbourNode, side) {
          let pNode = this,
              neighbourZoom = neighbourNode.segment.tileZoom,
              offset = 0;
  
          while (pNode.segment.tileZoom > neighbourZoom) {
              offset += PARTOFFSET[pNode.partId][side] / (1 << (pNode.segment.tileZoom - neighbourZoom));
              pNode = pNode.parentNode;
          }
  
          return offset;
      }
  }
  
  export { Node };
  