text: const mat4 = require('./mat4')
  
  const line2 = require('./line2')
  const line3 = require('./line3')
  
  const plane = require('./plane')
  
  const vec2 = require('./vec2')
  const vec3 = require('./vec3')
  
  /** class OrthoNormalBasis
   * Reprojects points on a 3D plane onto a 2D plane
   * or from a 2D plane back onto the 3D plane
   * @param  {Plane} plane
   * @param  {Vector3D|Vector2D} rightvector
   */
  const OrthoNormalBasis = function (plane, rightvector) {
    if (arguments.length < 2) {
      // choose an arbitrary right hand vector, making sure it is somewhat orthogonal to the plane normal:
      rightvector = vec3.random(plane)
    } else {
      rightvector = rightvector
    }
    this.v = vec3.unit(vec3.cross(plane, rightvector))
    this.u = vec3.cross(this.v, plane)
    this.plane = plane
    this.planeorigin = vec3.scale(plane[3], plane)
  }
  
  // Get an orthonormal basis for the standard XYZ planes.
  // Parameters: the names of two 3D axes. The 2d x axis will map to the first given 3D axis, the 2d y
  // axis will map to the second.
  // Prepend the axis with a "-" to invert the direction of this axis.
  // For example: OrthoNormalBasis.GetCartesian("-Y","Z")
  //   will return an orthonormal basis where the 2d X axis maps to the 3D inverted Y axis, and
  //   the 2d Y axis maps to the 3D Z axis.
  OrthoNormalBasis.GetCartesian = function (xaxisid, yaxisid) {
    let axisid = xaxisid + '/' + yaxisid
    let planenormal, rightvector
    if (axisid === 'X/Y') {
      planenormal = [0, 0, 1]
      rightvector = [1, 0, 0]
    } else if (axisid === 'Y/-X') {
      planenormal = [0, 0, 1]
      rightvector = [0, 1, 0]
    } else if (axisid === '-X/-Y') {
      planenormal = [0, 0, 1]
      rightvector = [-1, 0, 0]
    } else if (axisid === '-Y/X') {
      planenormal = [0, 0, 1]
      rightvector = [0, -1, 0]
    } else if (axisid === '-X/Y') {
      planenormal = [0, 0, -1]
      rightvector = [-1, 0, 0]
    } else if (axisid === '-Y/-X') {
      planenormal = [0, 0, -1]
      rightvector = [0, -1, 0]
    } else if (axisid === 'X/-Y') {
      planenormal = [0, 0, -1]
      rightvector = [1, 0, 0]
    } else if (axisid === 'Y/X') {
      planenormal = [0, 0, -1]
      rightvector = [0, 1, 0]
    } else if (axisid === 'X/Z') {
      planenormal = [0, -1, 0]
      rightvector = [1, 0, 0]
    } else if (axisid === 'Z/-X') {
      planenormal = [0, -1, 0]
      rightvector = [0, 0, 1]
    } else if (axisid === '-X/-Z') {
      planenormal = [0, -1, 0]
      rightvector = [-1, 0, 0]
    } else if (axisid === '-Z/X') {
      planenormal = [0, -1, 0]
      rightvector = [0, 0, -1]
    } else if (axisid === '-X/Z') {
      planenormal = [0, 1, 0]
      rightvector = [-1, 0, 0]
    } else if (axisid === '-Z/-X') {
      planenormal = [0, 1, 0]
      rightvector = [0, 0, -1]
    } else if (axisid === 'X/-Z') {
      planenormal = [0, 1, 0]
      rightvector = [1, 0, 0]
    } else if (axisid === 'Z/X') {
      planenormal = [0, 1, 0]
      rightvector = [0, 0, 1]
    } else if (axisid === 'Y/Z') {
      planenormal = [1, 0, 0]
      rightvector = [0, 1, 0]
    } else if (axisid === 'Z/-Y') {
      planenormal = [1, 0, 0]
      rightvector = [0, 0, 1]
    } else if (axisid === '-Y/-Z') {
      planenormal = [1, 0, 0]
      rightvector = [0, -1, 0]
    } else if (axisid === '-Z/Y') {
      planenormal = [1, 0, 0]
      rightvector = [0, 0, -1]
    } else if (axisid === '-Y/Z') {
      planenormal = [-1, 0, 0]
      rightvector = [0, -1, 0]
    } else if (axisid === '-Z/-Y') {
      planenormal = [-1, 0, 0]
      rightvector = [0, 0, -1]
    } else if (axisid === 'Y/-Z') {
      planenormal = [-1, 0, 0]
      rightvector = [0, 1, 0]
    } else if (axisid === 'Z/Y') {
      planenormal = [-1, 0, 0]
      rightvector = [0, 0, 1]
    } else {
      throw new Error('OrthoNormalBasis.GetCartesian: invalid combination of axis identifiers. Should pass two string arguments from [X,Y,Z,-X,-Y,-Z], being two different axes.')
    }
    return new OrthoNormalBasis(new Plane(new Vector3D(planenormal), 0), new Vector3D(rightvector))
  }
  
  /*
  // test code for OrthoNormalBasis.GetCartesian()
  OrthoNormalBasis.GetCartesian_Test=function() {
    let axisnames=["X","Y","Z","-X","-Y","-Z"];
    let axisvectors=[[1,0,0], [0,1,0], [0,0,1], [-1,0,0], [0,-1,0], [0,0,-1]];
    for(let axis1=0; axis1 < 3; axis1++) {
      for(let axis1inverted=0; axis1inverted < 2; axis1inverted++) {
        let axis1name=axisnames[axis1+3*axis1inverted];
        let axis1vector=axisvectors[axis1+3*axis1inverted];
        for(let axis2=0; axis2 < 3; axis2++) {
          if(axis2 != axis1) {
            for(let axis2inverted=0; axis2inverted < 2; axis2inverted++) {
              let axis2name=axisnames[axis2+3*axis2inverted];
              let axis2vector=axisvectors[axis2+3*axis2inverted];
              let orthobasis=OrthoNormalBasis.GetCartesian(axis1name, axis2name);
              let test1=orthobasis.to3D(new Vector2D([1,0]));
              let test2=orthobasis.to3D(new Vector2D([0,1]));
              let expected1=new Vector3D(axis1vector);
              let expected2=new Vector3D(axis2vector);
              let d1=test1.distanceTo(expected1);
              let d2=test2.distanceTo(expected2);
              if( (d1 > 0.01) || (d2 > 0.01) ) {
                throw new Error("Wrong!");
    }}}}}}
    throw new Error("OK");
  };
  */
  
  // The z=0 plane, with the 3D x and y vectors mapped to the 2D x and y vector
  OrthoNormalBasis.Z0Plane = function () {
    let plane = new Plane(new Vector3D([0, 0, 1]), 0)
    return new OrthoNormalBasis(plane, new Vector3D([1, 0, 0]))
  }
  
  OrthoNormalBasis.prototype = {
  
    getProjectionMatrix: function () {
      return mat4.fromValues(
        this.u[0], this.v[0], this.plane[0], 0,
        this.u[1], this.v[1], this.plane[1], 0,
        this.u[2], this.v[2], this.plane[2], 0,
        0, 0, -this.plane[3], 1
      )
    },
  
    getInverseProjectionMatrix: function () {
      let p = vec3.scale(this.plane[3], this.plane)
      return mat4.fromValues(
        this.u[0], this.u[1], this.u[2], 0,
        this.v[0], this.v[1], this.v[2], 0,
        this.plane[0], this.plane[1], this.plane[2], 0,
        p[0], p[1], p[2], 1
      )
    },
  
    to2D: function (point) {
      return vec2.fromValues(vec3.dot(point, this.u), vec3.dot(point, this.v))
    },
  
    to3D: function (point) {
      const v1 = vec3.scale(point[0], this.u)
      const v2 = vec3.scale(point[1], this.v)
  
      const v3 = vec3.add(this.planeorigin, v1)
      const v4 = vec3.add(v3, v2)
      return v4
    },
  
    line3Dto2D: function (line3d) {
      let a = line3d.point
      let b = line3d.direction.plus(a)
      let a2d = this.to2D(a)
      let b2d = this.to2D(b)
      return Line2D.fromPoints(a2d, b2d)
    },
  
    line2Dto3D: function (line2d) {
      let a = line2d.origin()
      let b = line2d.direction().plus(a)
      let a3d = this.to3D(a)
      let b3d = this.to3D(b)
      return Line3D.fromPoints(a3d, b3d)
    },
  
    transform: function (matrix4x4) {
      // todo: this may not work properly in case of mirroring
      let newplane = this.plane.transform(matrix4x4)
      let rightpointTransformed = this.u.transform(matrix4x4)
      let originTransformed = new Vector3D(0, 0, 0).transform(matrix4x4)
      let newrighthandvector = rightpointTransformed.minus(originTransformed)
      let newbasis = new OrthoNormalBasis(newplane, newrighthandvector)
      return newbasis
    }
  }
  
  module.exports = OrthoNormalBasis
  