/*
 * @Author: Xu.Wang
 * @Date: 2020-04-07 18:38:41
 * @Last Modified by: Xu.Wang
 * @Last Modified time: 2020-04-16 00:22:17
 */
import { Surface, SurfaceRayIntersection3 } from './surface'
import { Transform } from './transform'
import { Vector3, TS_EPSILON, TS_MAX_NUM } from '../math'
import { Ray } from './ray'
import { BoundingBox } from './bounding_box'

export class PhysicsPlane extends Surface {
  private _point: Vector3
  private _normal: Vector3

  constructor(
    normal: Vector3 = new Vector3(),
    point: Vector3 = new Vector3(),
    transform: Transform = new Transform(),
    isNormalFlipped: boolean = false
  ) {
    super(transform, isNormalFlipped)
    this._normal = normal
    this._point = point
  }

  point(): Vector3 {
    return this._point
  }
  normal(): Vector3 {
    return this._normal
  }

  setByPointWithNormal(point: Vector3, normal: Vector3) {
    this._normal = normal
    this._point = point
  }

  setByThreePoints(point1: Vector3, point2: Vector3, Vector3: Vector3) {
    this._normal = point2.sub(point1).cross(Vector3.sub(point1)).normalized()
    this._point = point1
  }

  isBounded(): boolean {
    return false
  }

  closestPointLocal(otherPoint: Vector3): Vector3 {
    let r = otherPoint.sub(this._point)
    return r.sub(this._normal.mul(this._normal.dot(r))).add(this._point)
  }

  closestNormalLocal(_: Vector3): Vector3 {
    return this._normal
  }

  intersectsLocal(ray: Ray): boolean {
    return Math.abs(ray.direction.dot(this._normal)) > 0
  }

  closestIntersectionLocal(ray: Ray): SurfaceRayIntersection3 {
    let intersection = new SurfaceRayIntersection3()

    let dDotN = ray.direction.dot(this._normal)

    // Check if not parallel
    if (Math.abs(dDotN) > 0) {
      let t = this._normal.dot(this._point.sub(ray.origin)) / dDotN
      if (t >= 0.0) {
        intersection.isIntersecting = true
        intersection.distance = t
        intersection.point = ray.pointAt(t)
        intersection.normal = this._normal
      }
    }

    return intersection
  }

  boundingBoxLocal(): BoundingBox {
    let eps = TS_EPSILON
    let dmax = TS_MAX_NUM

    if (Math.abs(this._normal.dot(new Vector3(1, 0, 0)) - 1.0) < eps) {
      return new BoundingBox(
        this._point.sub(new Vector3(0, dmax, dmax)),
        this._point.add(new Vector3(0, dmax, dmax))
      )
    } else if (Math.abs(this._normal.dot(new Vector3(0, 1, 0)) - 1.0) < eps) {
      return new BoundingBox(
        this._point.sub(new Vector3(dmax, 0, dmax)),
        this._point.add(new Vector3(dmax, 0, dmax))
      )
    } else if (Math.abs(this._normal.dot(new Vector3(0, 0, 1)) - 1.0) < eps) {
      return new BoundingBox(
        this._point.sub(new Vector3(dmax, dmax, 0)),
        this._point.add(new Vector3(dmax, dmax, 0))
      )
    } else {
      return new BoundingBox(
        new Vector3(dmax, dmax, dmax),
        new Vector3(dmax, dmax, dmax)
      )
    }
  }
}
