import { __read, __spread, __values } from "tslib";
import { isValue } from "@intelligentgraphics/ig.utilities";
import { Vector } from "./Vector";
import { getWorldTransformation, } from "../Types";
import { convertLineSegmentToLine, convertPlaneSegmentToPlane, } from "./MathTypes";
import { State } from "../State";
/**
 * Function to decide whether two components would collide if they were transformed according to the parameters
 * @param first The first component Id
 * @param second The second component Id
 * @param firstTransformation desired world position and rotation of first component
 * @param secondTransformation desired world position and rotation of second component
 * @param error
 */
export function wouldComponentsCollide(first, second, firstTransformation, secondTransformation, error) {
    if (error === void 0) { error = State.maxBBoxOverlap; }
    var tCollisionInfo = State.API.GetCollisionInfo(first, firstTransformation, second, secondTransformation, true, error);
    return tCollisionInfo;
    var updateComponentRegions = State.API.RegionManager.updateComponentRegions;
    State.API.RegionManager.updateComponentRegions = function () { return []; };
    //	console.group("IsCollision " + first + " " + second);
    //	console.log("Anfang ", first, Core.State.API.GetPosition(first).x,  second, Core.State.API.GetPosition(second).x);
    var tOldTransformationFirst = getWorldTransformation(first, true);
    var tOldTransformationSecond = getWorldTransformation(second, true);
    //	console.log(tOldTransformationFirst.position.x);
    //	console.log(tOldTransformationSecond.position.x);
    State.API.SetTransformation(first, firstTransformation);
    State.API.SetTransformation(second, secondTransformation);
    //	console.log("vor API.GetCollision ", first, Core.State.API.GetPosition(first).x,  second, Core.State.API.GetPosition(second).x);
    var tCollisionInformation = State.API.GetCollision(first, second, true, error);
    //	console.log("nach API.GetCollision ", first, Core.State.API.GetPosition(first).x,  second, Core.State.API.GetPosition(second).x);
    State.API.SetTransformation(first, tOldTransformationFirst);
    State.API.SetTransformation(second, tOldTransformationSecond);
    //	var tNewTransformationFirst = getWorldTransformation(first, true);
    //	var tNewTransformationSecond = getWorldTransformation(second, true);
    //	console.log(tNewTransformationFirst.position.x);
    //	console.log(tNewTransformationSecond.position.x);
    //	console.log("Ende ", first, Core.State.API.GetPosition(first).x,  second, Core.State.API.GetPosition(second).x);
    //	console.groupEnd();
    State.API.RegionManager.updateComponentRegions = updateComponentRegions;
    return tCollisionInformation;
}
/**
 * @param angleYRad The angle to rotate around the y axis. Given in radians.
 * @param previousPoint The previous position
 * @param fixPoint The fixed point of the rotation (the rotation center)
 */
export function AdaptToRotation(angleYRad, previousPoint, fixPoint) {
    var diffX = previousPoint.x - fixPoint.x;
    var diffZ = previousPoint.z - fixPoint.z;
    var x = diffX * Math.cos(angleYRad) + diffZ * Math.sin(angleYRad) + fixPoint.x;
    var y = previousPoint.y;
    var z = -diffX * Math.sin(angleYRad) + diffZ * Math.cos(angleYRad) + fixPoint.z;
    var result = Vector.CreateVec3(x, y, z);
    return result;
}
/**
 * Get the height of the passed Id. Calculated via the points (max - min)
 * TODO 2018-09-19 is this still working? (better use bounding box?)
 * @param componentId the id of the component
 * @returns its height
 */
export function GetHeight(componentId) {
    var tMin = 0;
    var tMax = 0;
    var points = State.API.GetPoints(componentId, undefined, undefined, undefined);
    points.forEach(function (pt) {
        // Math.min/max(y, null) === y !
        tMin = Math.min(pt.point.y, tMin);
        tMax = Math.max(pt.point.y, tMax);
    });
    // safe, because null - null === 0
    return tMax - tMin;
}
/**
 * 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
 */
export function GetIntersectionPoint(ray, plane) {
    var t = FactorOfDirectionFromRayToPlane(ray, plane);
    if (t === null) {
        return null;
    }
    var scaledDirection = Vector.ScalarMultiply3(t, ray.direction);
    var result = Vector.AddOnFirst3(scaledDirection, ray.origin);
    return result === undefined ? null : result;
}
/**
 * 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
 */
export function FactorOfDirectionFromRayToPlane(ray, plane) {
    var denominator = Vector.InnerProduct3(plane.normal, ray.direction);
    var planeConstant = -Vector.InnerProduct3(plane.point, plane.normal);
    if (denominator === 0) {
        // line is coplanar
        var distToPoint = Vector.InnerProduct3(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 = -(Vector.InnerProduct3(ray.origin, plane.normal) + planeConstant) /
        denominator;
    if (t >= 0) {
        return t;
    }
    // ray leads away from plane
    return null;
}
/**
 * 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}
 */
export function ProjectToPlane(point, plane) {
    var result = GetIntersectionPoint({ origin: point, direction: plane.normal }, plane);
    if (isValue(result)) {
        return result;
    }
    result = GetIntersectionPoint({ origin: point, direction: Vector.Reverse3(plane.normal) }, plane);
    return result;
}
/**
 * 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}
 */
export function IsInUpperHalfSpace(point, plane) {
    var direction = Vector.Sub3(point, plane.point);
    return Vector.InnerProduct3(direction, plane.normal) >= 0;
}
/**
 * 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}
 */
export function IsInLowerHalfSpace(point, plane) {
    var direction = Vector.Sub3(point, plane.point);
    return Vector.InnerProduct3(direction, plane.normal) <= 0;
}
/**
 * The distance between two objects.
 * Note that this function probably returns inconsistent values for boxes in general locations.
 * It works fine for boxes with just one rotation component.
 * TODO 2018-09-19 testing
 * @param firstComponentId
 * @param secondComponentId
 * @returns {number}
 */
export function DistanceOfObjects(firstComponentId, secondComponentId) {
    var firstBounds = State.API.GetAbstractBounds(firstComponentId);
    var secondBounds = State.API.GetAbstractBounds(secondComponentId);
    var firstPoints = firstBounds.getPoints();
    var firstDistances = firstPoints.map(function (pt) {
        return DistanceToAbstractBounds(secondBounds, pt);
    });
    var minFirstDistance = Math.min.apply(Math, __spread(firstDistances));
    var secondPoints = secondBounds.getPoints();
    var secondDistances = secondPoints.map(function (pt) {
        return DistanceToAbstractBounds(firstBounds, pt);
    });
    var minSecondDistance = Math.min.apply(Math, __spread(secondDistances));
    return Math.min(minFirstDistance, minSecondDistance);
}
/**
 * The middle of the bounding box of the object
 * @param componentId
 * @returns {vec3}
 */
export function MiddleOfObject(componentId) {
    var points = State.API.GetAbstractBounds(componentId).getPoints();
    var sum = Vector.Add3(points[0], points[7]);
    return Vector.ScalarMultiplyInPlace3(0.5, sum);
}
/**
 * The average middle point of all objects
 * @param componentIds
 * @returns {vec3}
 */
export function MiddleOfObjects(componentIds) {
    var theMiddles = componentIds.map(function (id) { return MiddleOfObject(id); });
    var sum = Vector.Add3.apply(Vector, __spread(theMiddles));
    var result = Vector.ScalarMultiplyInPlace3(1.0 / componentIds.length, sum);
    return result;
}
/**
 * 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}
 */
export function DistanceToPlane(point, plane) {
    //FIXME: null check for projectToPlane result
    var projectedOnPlane = ProjectToPlane(point, plane);
    return Vector.Distance3(projectedOnPlane, point);
}
/**
 * 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}
 */
export function ClosestPointOnLine(line, point) {
    var planeOfPoint = { point: point, normal: line.direction };
    return ProjectToPlane(line.point, planeOfPoint);
}
/**
 * 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}
 */
export function DistanceToPlaneSegment(segment, point) {
    var projected = ProjectToPlaneSegment(segment, point);
    var result = Vector.Distance3(point, projected);
    return result;
}
/**
 * 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}
 */
export function ProjectToPlaneSegment(segment, point) {
    var plane = convertPlaneSegmentToPlane(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;
}
/**
 * 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}
 */
export function DistanceToLineSegment(segment, point) {
    //FIXME: null check for ClosestPointOnLine
    var closestPoint = ClosestPointOnLine({ point: segment.point, direction: segment.border }, point);
    if (IsPointOnLineSegment(segment, closestPoint)) {
        return Vector.Distance3(point, closestPoint);
    }
    return Math.min(Vector.Distance3(point, segment.point), Vector.Distance3(point, Vector.Add3(segment.point, segment.border)));
}
/**
 * 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}
 */
export function IsPointOnLineSegment(segment, point) {
    var relativePoint = Vector.Sub3(point, segment.point);
    var relativeComponent = Vector.InnerProduct3(segment.border, relativePoint);
    var borderMagnitude = Vector.InnerProduct3(segment.border, segment.border);
    return 0 <= relativeComponent && relativeComponent <= borderMagnitude;
}
/**
 * Find the closest point to the given point on a line segment.
 * @param segment
 * @param point
 * @returns {vec3}
 */
export function ProjectToLineSegment(segment, point) {
    //FIXME: null check for ClosePointOnLine result
    var onLine = ClosestPointOnLine(convertLineSegmentToLine(segment), point);
    if (IsPointOnLineSegment(segment, onLine)) {
        return onLine;
    }
    var start = segment.point;
    var end = Vector.Add3(segment.point, segment.border);
    var distStart = Vector.Distance3(start, point);
    var distEnd = Vector.Distance3(end, point);
    if (distStart < distEnd) {
        return start;
    }
    return end;
}
/**
 * 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}
 */
export function IsInsidePlaneSegment(segment, point) {
    var relativePos = Vector.Sub3(point, segment.point);
    var firstBorder = segment.borderU;
    var firstComponent = Vector.InnerProduct3(firstBorder, relativePos);
    var firstMagnitude = Vector.InnerProduct3(firstBorder, firstBorder);
    var isInsideFirstLine = 0 <= firstComponent && firstComponent <= firstMagnitude;
    if (!isInsideFirstLine) {
        return false;
    }
    var secondBorder = segment.borderV;
    var secondComponent = Vector.InnerProduct3(secondBorder, relativePos);
    var secondMagnitude = Vector.InnerProduct3(secondBorder, secondBorder);
    var isInsideSecondLine = 0 <= secondComponent && secondComponent <= secondMagnitude;
    return isInsideFirstLine && isInsideSecondLine;
}
/**
 * Get the line segments of the plane segment, e.g. the borders of the segment
 * @param segment
 * @returns {LineSegment[]}
 */
export function LineSegmentsOfPlaneSegment(segment) {
    var otherPoint = Vector.Add3(segment.point, segment.borderU, segment.borderV);
    return [
        { point: segment.point, border: segment.borderU },
        { point: segment.point, border: segment.borderV },
        { point: otherPoint, border: Vector.Reverse3(segment.borderU) },
        { point: otherPoint, border: Vector.Reverse3(segment.borderV) },
    ];
}
/**
 * Gets the distance of a point the the abstract bounds.
 * For this get the minimal distance of all plane segments of the bounds.
 * @param bounds
 * @param point
 * @returns {number}
 */
export function DistanceToAbstractBounds(bounds, point) {
    var e_1, _a;
    if (IsPointInsideBounds(bounds, point)) {
        return 0;
    }
    var tPlaneSegments = bounds.getSegments();
    var tMinDistanceToPlaneSegment = Infinity;
    try {
        for (var tPlaneSegments_1 = __values(tPlaneSegments), tPlaneSegments_1_1 = tPlaneSegments_1.next(); !tPlaneSegments_1_1.done; tPlaneSegments_1_1 = tPlaneSegments_1.next()) {
            var tPlaneSegment = tPlaneSegments_1_1.value;
            var tDist = DistanceToPlaneSegment(tPlaneSegment, point);
            if (tDist < tMinDistanceToPlaneSegment) {
                tMinDistanceToPlaneSegment = tDist;
            }
        }
    }
    catch (e_1_1) { e_1 = { error: e_1_1 }; }
    finally {
        try {
            if (tPlaneSegments_1_1 && !tPlaneSegments_1_1.done && (_a = tPlaneSegments_1.return)) _a.call(tPlaneSegments_1);
        }
        finally { if (e_1) throw e_1.error; }
    }
    return tMinDistanceToPlaneSegment;
}
/**
 * Check whether a point is inside the given bounds.
 * @param bounds
 * @param point
 * @returns {boolean}
 */
export function IsPointInsideBounds(bounds, point) {
    var directions = bounds.getDirections();
    var basePoint = bounds.getDirectionBasePoint();
    var relativePosition = Vector.Sub3(point, basePoint);
    // same algorithm as for plane and line segments
    var result = directions.every(function (dir) {
        var share = Vector.InnerProduct3(dir, relativePosition);
        var dot = Vector.InnerProduct3(dir, dir);
        return 0 <= share && share <= dot;
    });
    return result;
}
/**
 * Rotation of vector a around vector b considering c as axis and
 * @param a
 * @param b
 * @param n
 * @param angle
 */
export function rotateVectorAAroundPointBByNormalCWithRadiantAngle(a, b, n, angle) {
    var n1 = n.x;
    var n2 = n.y;
    var n3 = n.z;
    var _1_cosa = 1 - Math.cos(angle);
    var sina = Math.sin(angle);
    var cosa = Math.cos(angle);
    // multiply rotation matrix with vector
    var multMat = function (m, a) {
        return {
            x: m[0][0] * a.x + m[0][1] * a.y + m[0][2] * a.z,
            y: m[1][0] * a.x + m[1][1] * a.y + m[1][2] * a.z,
            z: m[2][0] * a.x + m[2][1] * a.y + m[2][2] * a.z,
        };
    };
    // rotation around axis n
    var rotMat = [
        [
            n1 * n1 * _1_cosa + cosa,
            n1 * n2 * _1_cosa - n3 * sina,
            n1 * n3 * _1_cosa + n2 * sina,
        ],
        [
            n2 * n1 * _1_cosa + n3 * sina,
            n2 * n2 * _1_cosa + cosa,
            n2 * n3 * _1_cosa - n1 * sina,
        ],
        [
            n3 * n1 * _1_cosa - n2 * sina,
            n3 * n2 * _1_cosa + n1 * sina,
            n3 * n3 * _1_cosa + cosa,
        ],
    ];
    //console.log("rotationsmatrix", rotMat);
    // 2 rotate
    var rotated = Vector.Add3(b, multMat(rotMat, Vector.Sub3(a, b)));
    /*
    FourBar.dlog(
      "RotateVectorAaroundPointBbyNormalCangleRadR",
      a,
      b,
      n,
      ang,
      "-->",
      rotated
    );
    */
    return rotated;
}
/*
    return -2 for negative factor + 2 for positive factor or null for parallel
    */
export function getOrderedIntersectionFactor(ray, plane) {
    var f1 = FactorOfDirectionFromRayToPlane(ray, plane);
    if (f1 === null) {
        var f2 = FactorOfDirectionFromRayToPlane({
            origin: ray.origin,
            direction: Vector.ScalarMultiply3(-1, ray.direction),
        }, plane);
        if (f2 === null) {
            return null;
        }
        else {
            return -f2;
        }
    }
    else {
        return f1;
    }
}
// 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
export function intersectPlanes(p1, p2) {
    if (!p1) {
        return null;
    }
    if (!p2) {
        return null;
    }
    var intersectionlinedirection = Vector.Cross3(p1.normal, p2.normal);
    var planesAreParallel = Vector.Magnitude3(intersectionlinedirection) < 0.000001;
    if (planesAreParallel) {
        return null;
    }
    intersectionlinedirection = Vector.Normalize3(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 = Vector.InnerProduct3(N1, N1) * Vector.InnerProduct3(N2, N2) -
        Vector.InnerProduct3(N1, N2) * Vector.InnerProduct3(N1, N2);
    var c1s = (d1s * Vector.InnerProduct3(N2, N2) -
        d2s * Vector.InnerProduct3(N1, N2)) /
        determinant;
    var c2s = (d2s * Vector.InnerProduct3(N1, N1) -
        d1s * Vector.InnerProduct3(N1, N2)) /
        determinant;
    //p = c1 N1 + c2 N2 + u N1 * N2
    var p = Vector.Add3(Vector.ScalarMultiply3(c1s, N1), Vector.ScalarMultiply3(c2s, N2)); // + u N1 * N2
    Z = p;
    console.log("origin:", Z, "direction:", intersectionlinedirection);
    return {
        origin: Z,
        direction: intersectionlinedirection,
    };
}
