"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Vector3_1 = require("./Vector3");
var Matrix3_1 = require("./Matrix3");
var Line_1 = require("./Line");
var Plane_1 = require("./Plane");
// import { Transformation } from "./Transformation";
/**
 * Get the intersection point of the ray and the plane if there is one
 * @param ray the ray
 * @param plane the plane
 * @returns WorldCoordinates or null if there is no intersection point, null if direction leads away
 */
function getIntersectionPoint(ray, plane) {
    var t = factorOfDirectionFromRayToPlane(ray, plane);
    if (t === null) {
        return null;
    }
    var scaledDirection = Vector3_1.Vector3.scalarMultiply(t, ray.direction);
    var result = Vector3_1.Vector3.addOnFirst(scaledDirection, ray.origin);
    return result === undefined ? null : result;
}
exports.getIntersectionPoint = getIntersectionPoint;
/**
 * Gets a factor t so that (ray.direction * t) is an intersection point on the plane.
 * If the ray is parallel to the plane or leads away from it, it returns null.
 * @param ray
 * @param plane
 */
function factorOfDirectionFromRayToPlane(ray, plane) {
    var denominator = Vector3_1.Vector3.innerProduct(plane.normal, ray.direction);
    var planeConstant = -Vector3_1.Vector3.innerProduct(plane.point, plane.normal);
    if (denominator === 0) {
        // line is coplanar
        var distToPoint = Vector3_1.Vector3.innerProduct(plane.normal, ray.origin) + planeConstant;
        if (distToPoint === 0) {
            // ray is originating directly from the plane
            return 0;
        }
        // ray parallel to plane, so it will never hit the plane
        return null;
    }
    var t = -(Vector3_1.Vector3.innerProduct(ray.origin, plane.normal) + planeConstant) /
        denominator;
    if (t >= 0) {
        return t;
    }
    // ray leads away from plane
    return null;
}
exports.factorOfDirectionFromRayToPlane = factorOfDirectionFromRayToPlane;
/**
 * Projects the point to the plane, returning the point in the plane that is closest to the given point.
 * @param point
 * @param plane
 * @returns {vec3}
 */
function projectToPlane(point, plane) {
    var result = getIntersectionPoint({ origin: point, direction: plane.normal }, plane);
    if (result) {
        return result;
    }
    result = getIntersectionPoint({ origin: point, direction: Vector3_1.Vector3.reverse(plane.normal) }, plane);
    return result;
}
exports.projectToPlane = projectToPlane;
/**
 * Determine if the given point is in the upper half space of the plane.
 * Note that this depends on the direction of the plane normal.
 * @param point
 * @param plane
 * @returns {boolean}
 */
function isInUpperHalfSpace(point, plane) {
    var direction = Vector3_1.Vector3.sub(point, plane.point);
    return Vector3_1.Vector3.innerProduct(direction, plane.normal) >= 0;
}
exports.isInUpperHalfSpace = isInUpperHalfSpace;
/**
 * Determine if the given point is in the lower half space of the plane.
 * Note that this depends on the direction of the plane normal.
 * @param point
 * @param plane
 * @returns {boolean}
 */
function isInLowerHalfSpace(point, plane) {
    var direction = Vector3_1.Vector3.sub(point, plane.point);
    return Vector3_1.Vector3.innerProduct(direction, plane.normal) <= 0;
}
exports.isInLowerHalfSpace = isInLowerHalfSpace;
/**
 * The distance of a point to the plane.
 * Calculated by the distance of the point to its projection on the plane.
 * @param point
 * @param plane
 * @returns {number}
 */
function distanceToPlane(point, plane) {
    //FIXME: null check for projectToPlane result
    var projectedOnPlane = projectToPlane(point, plane);
    return Vector3_1.Vector3.distance(projectedOnPlane, point);
}
exports.distanceToPlane = distanceToPlane;
/**
 * Finds the point on the line with minimal distance to the given point.
 * For this, use the direction of the line as a normal vector for a plane containing the point.
 * The closest point then is the projection of the line to this plane.
 * @param line
 * @param point
 * @returns {vec3}
 */
function closestPointOnLine(line, point) {
    var planeOfPoint = { point: point, normal: line.direction };
    return projectToPlane(line.point, planeOfPoint);
}
exports.closestPointOnLine = closestPointOnLine;
/**
 * Calulcates the distance a point has to the plane segment.
 * There are two cases:
 * 		- The point projected to the plane of the segment is inside the plane segment.
 * 		Then the distance is the distance the point has to its projection
 * 		- Otherwise the projection is not in the segment. Calculate the distance to the closest border of the segment.
 * 		Applying the pythagorean rule we obtain the distance.
 *
 * 		, .
 * 		, |\
 * 		, | \
 * 		a |  \  c
 * 		, |   \
 * 		, |    \
 * 		, p-----
 * 		, _______
 * 				b
 * 		. is the point in 3D space
 * 		p is the projection to the plane of the segment
 * 		_____ is the segment
 * 		Pythargoras: a^2 + b^2 = c^2
 * @param segment
 * @param point
 * @returns {number}
 */
function distanceToPlaneSegment(segment, point) {
    var projected = projectToPlaneSegment(segment, point);
    var result = Vector3_1.Vector3.distance(point, projected);
    return result;
}
exports.distanceToPlaneSegment = distanceToPlaneSegment;
/**
 * Projects the point to the plane segment.
 * For this, we first project the point to the plane containing the plane segment.
 * Then there are two cases to consider:
 * 		a) the point is already inside the plane segment
 * 		b) the point is not in the plane segment
 *
 * 	In the first case the projection is the same as the projection to the plane.
 * 	In the second case we also have to consider the borders of the segment and project to them,
 * 	choosing the smallest distance line segment.
 * @param segment
 * @param point
 * @returns {vec3}
 */
function projectToPlaneSegment(segment, point) {
    var plane = Plane_1.PlaneSegment.convertToPlane(segment);
    //FIXME: null check for projectToPlane result
    var projection = projectToPlane(point, plane);
    if (isInsidePlaneSegment(segment, projection)) {
        return projection;
    }
    var lineSegments = lineSegmentsOfPlaneSegment(segment);
    var minDistanceValue = distanceToLineSegment(lineSegments[0], projection);
    var minDistanceIndex = 0;
    for (var lIdx = 1; lIdx < lineSegments.length; lIdx++) {
        var distanceValue = distanceToLineSegment(lineSegments[lIdx], projection);
        if (distanceValue < minDistanceValue) {
            minDistanceValue = distanceValue;
            minDistanceIndex = lIdx;
        }
    }
    var closestSegment = lineSegments[minDistanceIndex];
    var result = projectToLineSegment(closestSegment, projection);
    return result;
}
exports.projectToPlaneSegment = projectToPlaneSegment;
/**
 * Calculate the distance to the line segment.
 * This is either the closest point on the line containing the segment, or one of the border points.
 * @param segment
 * @param point
 * @returns {number}
 */
function distanceToLineSegment(segment, point) {
    //FIXME: null check for ClosestPointOnLine
    var closestPoint = closestPointOnLine({ point: segment.point, direction: segment.border }, point);
    if (isPointOnLineSegment(segment, closestPoint)) {
        return Vector3_1.Vector3.distance(point, closestPoint);
    }
    return Math.min(Vector3_1.Vector3.distance(point, segment.point), Vector3_1.Vector3.distance(point, Vector3_1.Vector3.add(segment.point, segment.border)));
}
exports.distanceToLineSegment = distanceToLineSegment;
/**
 * Check if point is on the line segment. Assumes that the point is already on the line containing the segment.
 * For this, effectively project the point to the line and check if the projection is between the two points.
 * This can be achieved by comparing innerProducts.
 * @param segment
 * @param point
 * @returns {boolean}
 */
function isPointOnLineSegment(segment, point) {
    var relativePoint = Vector3_1.Vector3.sub(point, segment.point);
    var relativeComponent = Vector3_1.Vector3.innerProduct(segment.border, relativePoint);
    var borderMagnitude = Vector3_1.Vector3.innerProduct(segment.border, segment.border);
    return 0 <= relativeComponent && relativeComponent <= borderMagnitude;
}
exports.isPointOnLineSegment = isPointOnLineSegment;
/**
 * Find the closest point to the given point on a line segment.
 * @param segment
 * @param point
 * @returns {vec3}
 */
function projectToLineSegment(segment, point) {
    //FIXME: null check for ClosePointOnLine result
    var onLine = closestPointOnLine(Line_1.LineSegment.convertToLine(segment), point);
    if (isPointOnLineSegment(segment, onLine)) {
        return onLine;
    }
    var start = segment.point;
    var end = Vector3_1.Vector3.add(segment.point, segment.border);
    var distStart = Vector3_1.Vector3.distance(start, point);
    var distEnd = Vector3_1.Vector3.distance(end, point);
    if (distStart < distEnd) {
        return start;
    }
    return end;
}
exports.projectToLineSegment = projectToLineSegment;
/**
 * Checks if the point is contained in the plane segment. This assumes that the point is already in the plane of the segment!
 * @param segment
 * @param point
 * @returns {boolean}
 */
function isInsidePlaneSegment(segment, point) {
    var relativePos = Vector3_1.Vector3.sub(point, segment.point);
    var firstBorder = segment.borderU;
    var firstComponent = Vector3_1.Vector3.innerProduct(firstBorder, relativePos);
    var firstMagnitude = Vector3_1.Vector3.innerProduct(firstBorder, firstBorder);
    var isInsideFirstLine = 0 <= firstComponent && firstComponent <= firstMagnitude;
    if (!isInsideFirstLine) {
        return false;
    }
    var secondBorder = segment.borderV;
    var secondComponent = Vector3_1.Vector3.innerProduct(secondBorder, relativePos);
    var secondMagnitude = Vector3_1.Vector3.innerProduct(secondBorder, secondBorder);
    var isInsideSecondLine = 0 <= secondComponent && secondComponent <= secondMagnitude;
    return isInsideFirstLine && isInsideSecondLine;
}
exports.isInsidePlaneSegment = isInsidePlaneSegment;
/**
 * Get the line segments of the plane segment, e.g. the borders of the segment
 * @param segment
 * @returns {LineSegment[]}
 */
function lineSegmentsOfPlaneSegment(segment) {
    var otherPoint = Vector3_1.Vector3.add(segment.point, segment.borderU, segment.borderV);
    return [
        { point: segment.point, border: segment.borderU },
        { point: segment.point, border: segment.borderV },
        { point: otherPoint, border: Vector3_1.Vector3.reverse(segment.borderU) },
        { point: otherPoint, border: Vector3_1.Vector3.reverse(segment.borderV) },
    ];
}
exports.lineSegmentsOfPlaneSegment = lineSegmentsOfPlaneSegment;
/**
 * Rotation of vector a around vector b considering c as axis and
 * @param vectorA
 * @param vectorB
 * @param normalVector
 * @param angle in Radians
 */
function rotateVectorAAroundPointBByNormalCWithRadiantAngle(vectorA, vectorB, normalVector, angle) {
    // rotation around axis n
    var rotMat = Matrix3_1.Matrix3.makeRotationMatrixFromNormalVector(normalVector, angle);
    var vecABdiff = Vector3_1.Vector3.sub(vectorA, vectorB);
    var rotatedVec = Matrix3_1.Matrix3.multiplyWithVector3(vecABdiff, rotMat);
    // 2 rotate
    var rotated = Vector3_1.Vector3.add(vectorB, rotatedVec);
    return rotated;
}
exports.rotateVectorAAroundPointBByNormalCWithRadiantAngle = rotateVectorAAroundPointBByNormalCWithRadiantAngle;
/**
 * return -2 for negative factor + 2 for positive factor or null for parallel
 * @param ray Ray
 * @param plane Plane
 */
function getOrderedIntersectionFactor(ray, plane) {
    var f1 = factorOfDirectionFromRayToPlane(ray, plane);
    if (f1 === null) {
        var f2 = factorOfDirectionFromRayToPlane({
            origin: ray.origin,
            direction: Vector3_1.Vector3.scalarMultiply(-1, ray.direction),
        }, plane);
        if (f2 === null) {
            return null;
        }
        else {
            return -f2;
        }
    }
    else {
        return f1;
    }
}
exports.getOrderedIntersectionFactor = getOrderedIntersectionFactor;
// returns ray or null
//based on http://geomalgorithms.com/a05-_intersect-1.html
//https://en.wikipedia.org/wiki/Plane_(geometry)#Line_of_intersection_between_two_planes
function intersectPlanes(p1, p2) {
    if (!p1) {
        return null;
    }
    if (!p2) {
        return null;
    }
    var intersectionlinedirection = Vector3_1.Vector3.crossProduct(p1.normal, p2.normal);
    var planesAreParallel = Vector3_1.Vector3.magnitude(intersectionlinedirection) < 0.000001;
    if (planesAreParallel) {
        return null;
    }
    intersectionlinedirection = Vector3_1.Vector3.normalize(intersectionlinedirection);
    // find base point Z (Ziel)
    var Z = { x: 0, y: 0, z: 0 };
    // plane origins
    var x1o = p1.point.x;
    var y1o = p1.point.y;
    var z1o = p1.point.z;
    var x2o = p2.point.x;
    var y2o = p2.point.y;
    var z2o = p2.point.z;
    var a1 = p1.normal.x;
    var b1 = p1.normal.y;
    var c1 = p1.normal.z;
    var a2 = p2.normal.x;
    var b2 = p2.normal.y;
    var c2 = p2.normal.z;
    // form     ax + by + cz + d = 0
    var d1 = -(a1 * x1o + b1 * y1o + c1 * z1o);
    var d2 = -(a2 * x2o + b2 * y2o + c2 * z2o);
    // naechster versuch
    //http://paulbourke.net/geometry/pointlineplane/
    var d1s = -d1;
    var d2s = -d2;
    var N1 = p1.normal;
    var N2 = p2.normal;
    //let determinant = ( N1 . N1 ) ( N2 . N2 ) - ( N1 . N2 )^2
    var determinant = Vector3_1.Vector3.innerProduct(N1, N1) * Vector3_1.Vector3.innerProduct(N2, N2) -
        Vector3_1.Vector3.innerProduct(N1, N2) * Vector3_1.Vector3.innerProduct(N1, N2);
    var c1s = (d1s * Vector3_1.Vector3.innerProduct(N2, N2) -
        d2s * Vector3_1.Vector3.innerProduct(N1, N2)) /
        determinant;
    var c2s = (d2s * Vector3_1.Vector3.innerProduct(N1, N1) -
        d1s * Vector3_1.Vector3.innerProduct(N1, N2)) /
        determinant;
    //p = c1 N1 + c2 N2 + u N1 * N2
    var p = Vector3_1.Vector3.add(Vector3_1.Vector3.scalarMultiply(c1s, N1), Vector3_1.Vector3.scalarMultiply(c2s, N2)); // + u N1 * N2
    Z = p;
    console.log("origin:", Z, "direction:", intersectionlinedirection);
    return {
        origin: Z,
        direction: intersectionlinedirection,
    };
}
exports.intersectPlanes = intersectPlanes;
function intersectionPoint(ray, plane) {
    var t = factorOfDirectionFromRayToPlane(ray, plane);
    if (t === null) {
        return null;
    }
    var scaledDirection = Vector3_1.Vector3.scalarMultiply(t, ray.direction);
    var result = Vector3_1.Vector3.add(scaledDirection, ray.origin);
    return result;
}
exports.intersectionPoint = intersectionPoint;
