import { Quaternion } from "./quaternion";
import { lerp } from "./math";
/**
 * @remarks A class to extend Minecraft's Vector3 with additional functions for vector math.
 */
export class Vector3 {
    /**
     * @remarks Create a new Vector3.
     * @param value The value to crate a new Vector3. Either a single value used for x, y and z. Or an object with an x, y and z value.
     * @example
     * Number Example
     * ```typescript
     * const vector = new Vector3(0); // Creates a vector with value {x: 0, y: 0, z: 0};
     * ```
     * @example
     * Vector Example
     * ```typescript
     * const vector = new Vector3({x: 0, y: 0, z: 0});
     * ```
     */
    constructor(value) {
        if (typeof value === 'object' && value !== null) {
            this.x = value.x;
            this.y = value.y;
            this.z = value.z;
        }
        else {
            this.x = value;
            this.y = value;
            this.z = value;
        }
    }
    /**
     * @remarks Adds another Vector3 to this Vector3.
     * @param value The Vector3 to be added to this.
     * @returns A new Vector3.
     * @example
     * ```typescript
     * vector = vector.add({x: 0, y: 10, z: 0}); // Creates a vector where y is 10 higher.
     * ```
     */
    add(value) {
        if (typeof value === 'object' && value !== null) {
            return new Vector3({ x: this.x + value.x, y: this.y + value.y, z: this.z + value.z });
        }
        else {
            return new Vector3({ x: this.x + value, y: this.y + value, z: this.z + value });
        }
    }
    /**
     * @remarks Subtracts another Vector3 from this Vector3.
     * @param value The Vector3 to be subtracted from this.
     * @returns A new Vector3.
     * @example
     * ```typescript
     * vector = vector.subtract({x: 0, y: 10, z: 0}); // Creates a vector where y is 10 lower.
     * ```
     */
    subtract(value) {
        if (typeof value === 'object' && value !== null) {
            return new Vector3({ x: this.x - value.x, y: this.y - value.y, z: this.z - value.z });
        }
        else {
            return new Vector3({ x: this.x - value, y: this.y - value, z: this.z - value });
        }
    }
    /**
     * @remarks Multiplies another Vector3 by this Vector3.
     * @param value The Vector3 to be multiplied to this.
     * @returns A new Vector3.
     * @example
     * ```typescript
     * new Vector3(1).multiply(10); // Creates the vector {x: 10, y: 10, z: 10}
     * ```
     */
    multiply(value) {
        if (typeof value === 'object' && value !== null) {
            return new Vector3({ x: this.x * value.x, y: this.y * value.y, z: this.z * value.z });
        }
        else {
            return new Vector3({ x: this.x * value, y: this.y * value, z: this.z * value });
        }
    }
    /**
     * @remarks Divides another Vector3 by this Vector3.
     * @param value The Vector3 to be divided by this.
     * @returns A new Vector3.
     * ```typescript
     * new Vector3(10).divide(10); // Creates the vector {x: 1, y: 1, z: 1}
     * ```
     */
    divide(value) {
        if (typeof value === 'object' && value !== null) {
            return new Vector3({ x: this.x / value.x, y: this.y / value.y, z: this.z / value.z });
        }
        else {
            return new Vector3({ x: this.x / value, y: this.y / value, z: this.z / value });
        }
    }
    /**
     * @remarks Normalizes this vector.
     * @returns This vector normalized.
     * @example
     * ```typescript
     * vector = vector.normalize();
     * ```
     */
    normalize() {
        return this.divide(this.length() || 1);
    }
    /**
     * @remarks Calculates the dot product of this vector against another.
     * @param vector The vector to calculate the dot product with.
     * @returns A number representing the dot product.
     */
    dot(vector) {
        return this.x * vector.x + this.y * vector.y + this.z * vector.z;
    }
    /**
     * @remarks Calculates the cross vector of this vector against another.
     * @param vector The vector to calculate the cross vector with.
     * @returns The cross of the two vectors.
     */
    cross(vector) {
        const ax = this.x, ay = this.y, az = this.z;
        const bx = vector.x, by = vector.y, bz = vector.z;
        const x = ay * bz - az * by;
        const y = az * bx - ax * bz;
        const z = ax * by - ay * bx;
        return new Vector3({ x, y, z });
    }
    /**
     * @remarks Linearly interpolates between two vectors.
     * @param vector The destination vector.
     * @param alpha The alpha of interpolation between the two, as 0-1.
     * @returns The Vector at the point of the alpha.
     */
    lerp(vector, alpha) {
        const x = lerp(this.x, vector.x, alpha);
        const y = lerp(this.y, vector.y, alpha);
        const z = lerp(this.z, vector.z, alpha);
        return new Vector3({ x, y, z });
    }
    /**
     * @remarks Gets the distance between this and a target vector.
     * @param vector The vector to check the distance against.
     * @returns The distance between this and the target vector.
     * @example
     * ```typescript
     * function getDistance(source: SafetyCheckedEntity, target: SafetyCheckedEntity): number {
     * 	return source.location.distance(target.location);
     * }
     * ```
     */
    distance(vector) {
        const x = (this.x - vector.x) ** 2;
        const y = (this.y - vector.y) ** 2;
        const z = (this.z - vector.z) ** 2;
        return Math.sqrt(x + y + z);
    }
    /**
     * @remarks Gets the length of this Vector, i.e. the distance from 0 0 0.
     * @returns This Vector's length.
     */
    length() {
        return Vector3.ZERO.distance(this);
    }
    /**
     * @remarks Applies a quaternion to the vector.
     * @param quaternion The quaternion to apply.
     * @returns The vector modified by a quaternion.
     */
    applyQuaternion(quaternion) {
        const vx = this.x;
        const vy = this.y;
        const vz = this.z;
        const qx = quaternion.X;
        const qy = quaternion.Y;
        const qz = quaternion.Z;
        const qw = quaternion.W;
        const tx = 2 * (qy * vz - qz * vy);
        const ty = 2 * (qz * vx - qx * vz);
        const tz = 2 * (qx * vy - qy * vx);
        const x = vx + qw * tx + qy * tz - qz * ty;
        const y = vy + qw * ty + qz * tx - qx * tz;
        const z = vz + qw * tz + qx * ty - qy * tx;
        return new Vector3({ x, y, z });
    }
    /**
     * @deprecated This function has been deprecated in favor of {@link rotateYaw} to better differentiate the direction of rotation.
     * @remarks Rotates this vector by a provided angle.
     * @param angle The angle by which to rotate the vector.
     * @returns The rotated vector.
     */
    rotateByAngle(angle) {
        return this.rotateYaw(angle);
    }
    /**
     * @remarks Rotates this vector on the yaw (y) axis by a provided angle.
     * @param angle The angle by which to rotate the vector.
     * @returns The rotated vector.
     * @example
     * ```typescript
     * for (let angle = look_angle; angle < look_angle + 360; angle += 45) {
     * 	const direction = needle_hog.getViewDirection().rotateByAngleYaw(angle);
     * 	const origin = needle_hog.getHeadLocation().add(direction.multiply(0.5));
     * 	const projectile = new utils.SafetyCheckedEntity(event.sourceEntity.dimension.spawnEntity(projectile_ID, origin));
     * 	projectile.applyImpulse(direction.multiply(2));
     * 	projectile.setRotation({x: 0, y: angle});
     * }
     * ```
     */
    rotateYaw(angle, crossAxis = Vector3.UP) {
        const quaternion = Quaternion.fromAxisAngle(crossAxis, angle * (Math.PI / 180));
        return this.applyQuaternion(quaternion);
    }
    /**
     * @remarks Rotates this vector on the pitch (x) axis by a provided angle.
     * @param angle The angle by which to rotate the vector.
     * @returns The rotated vector.
     * @example
     * ```typescript
     * for (let angle = look_angle; angle < look_angle + 360; angle += 45) {
     * 	const direction = needle_hog.getViewDirection().rotateByAnglePitch(angle);
     * 	const origin = needle_hog.getHeadLocation().add(direction.multiply(0.5));
     * 	const projectile = new utils.SafetyCheckedEntity(event.sourceEntity.dimension.spawnEntity(projectile_ID, origin));
     * 	projectile.applyImpulse(direction.multiply(2));
     * 	projectile.setRotation({x: 0, y: angle});
     * }
     * ```
     */
    rotatePitch(angle) {
        const qAxis = Vector3.UP.cross(this).normalize();
        const qAngle = Math.acos(Vector3.UP.dot(this)) + (angle * (Math.PI / 180));
        const quaternion = Quaternion.fromAxisAngle(qAxis, qAngle);
        return Vector3.UP.applyQuaternion(quaternion);
    }
    /**
     * @remarks Rounds a Vector3 to whole numbers.
     * @returns This Vector3 rounded to the nearest whole number.
     * @example
     * ```typescript
     * const vector = new Vector3({x: 0.6, y: 10.2, z: 14.9}).round(); // Returns a vector with the value {x: 1, y: 10, z: 15}
     * ```
     */
    round() {
        const x = Math.round(this.x);
        const y = Math.round(this.y);
        const z = Math.round(this.z);
        return new Vector3({ x, y, z });
    }
    /**
     * @remarks Floors a Vector3 to whole numbers.
     * @returns This Vector3 floored to the nearest whole number.
     * @example
     * ```typescript
     * const vector = new Vector3({x: 0.6, y: 10.2, z: 14.9}).floor(); // Returns a vector with the value {x: 0, y: 10, z: 14}
     * ```
     */
    floor() {
        const x = Math.floor(this.x);
        const y = Math.floor(this.y);
        const z = Math.floor(this.z);
        return new Vector3({ x, y, z });
    }
    /**
     * @remarks Ceils a Vector3 to whole numbers.
     * @returns This Vector3 ceiled to the nearest whole number.
     * @example
     * ```typescript
     * const vector = new Vector3({x: 0.6, y: 10.2, z: 14.9}).ceil(); // Returns a vector with the value {x: 1, y: 11, z: 15}
     * ```
     */
    ceil() {
        const x = Math.ceil(this.x);
        const y = Math.ceil(this.y);
        const z = Math.ceil(this.z);
        return new Vector3({ x, y, z });
    }
    /**
     * @remarks Returns a block center, useful for spawning mobs at the center of a block.
     * @returns This Vector3 centered.
     * @example
     * ```typescript
     * const vector = new Vector3({x: 0.6, y: 10.2, z: 14.9}).center(); // Returns a vector with the value {x: 0.5, y: 10, z: 14.5}
     * ```
     */
    center() {
        return this.floor().add({ x: 0.5, y: 0, z: 0.5 });
    }
    /**
     * @remarks Returns a Vector3 some blocks up of this Vector3.
     * @param [amount=1] The number of blocks up from this Vector3 to return, defaults to 1.
     * @returns The location above this Vector.
     */
    up(amount = 1) {
        return this.add(Vector3.UP.multiply(amount));
    }
    /**
     * @remarks Returns a Vector3 one block down of this Vector3.
     * @param [amount=1] The number of blocks down from this Vector3 to return, defaults to 1.
     * @returns The location below this Vector.
     */
    down(amount = 1) {
        return this.add(Vector3.DOWN.multiply(amount));
    }
    /**
     * @remarks Returns a Vector3 none block north of this Vector3.
     * @param [amount=1] The number of blocks north from this Vector3 to return, defaults to 1.
     * @returns The location north of this Vector.
     */
    north(amount = 1) {
        return this.add(Vector3.NORTH.multiply(amount));
    }
    /**
     * @remarks Returns a Vector3 sone block south of this Vector3.
     * @param [amount=1] The number of blocks south from this Vector3 to return, defaults to 1.
     * @returns The location south of this Vector.
     */
    south(amount = 1) {
        return this.add(Vector3.SOUTH.multiply(amount));
    }
    /**
     * @remarks Returns a Vector3 one block east of this Vector3.
     * @param [amount=1] The number of blocks east from this Vector3 to return, defaults to 1.
     * @returns The location east of this Vector.
     */
    east(amount = 1) {
        return this.add(Vector3.EAST.multiply(amount));
    }
    /**
     * @remarks Returns a Vector3 one block west of this Vector3.
     * @param [amount=1] The number of blocks west from this Vector3 to return, defaults to 1.
     * @returns The location west of this Vector.
     */
    west(amount = 1) {
        return this.add(Vector3.WEST.multiply(amount));
    }
    /**
     * @remarks Checks if two vectors are approximately equal.
     * @param vector The vector to compare
     * @returns True if these vectors are very close to eachother. False otherwise.
     * @example
     * ```typescript
     * const locationA = new Vector3(1.1);
     * const locationB = new Vector3(1.2);
     * const locationC = new Vector3(4.0);
     * locationA.approximatelyEqual(locationB); // Returns true.
     * locationA.approximatelyEqual(locationC); // Returns false.
     * ```
     */
    approximatelyEqual(vector) {
        const originX = Math.floor(this.x);
        const originY = Math.floor(this.y);
        const originZ = Math.floor(this.z);
        const targetX = Math.floor(vector.x);
        const targetY = Math.floor(vector.y);
        const targetZ = Math.floor(vector.z);
        return (originX === targetX) && (originY === targetY) && (originZ === targetZ);
    }
    /**
     * @remarks Checks if this vector is within the provided bounds volume.
     * @param minLocation The negative-most coordinate of the volume (inclusive).
     * @param maxLocation The positive-most coordinate of the volume (inclusive).
     * @returns True if this vector is within the bounds, false otherwise.
     * @example
     * ```typescript
     * const myLocation = new Vector3(1);
     * myLocation.inBounds(new Vector3(0), new Vector3(2)); // returns true
     * ```
     */
    inBounds(minLocation, maxLocation) {
        return ((this.x >= minLocation.x && this.y >= minLocation.y && this.z >= minLocation.z) &&
            (this.x <= maxLocation.x && this.y <= maxLocation.y && this.z <= maxLocation.z));
    }
    /**
     * @remarks Gets the chunk origin of this location
     * @returns A Vector3 representing the origin of the chunk.
     * @example
     * ```typescript
     * const location = new Vector3({x: 24, y: 100, z: 68});
     * const chunk = location.toChunk(); // Returns a Vector3 with the value {x: 16, y: 0, z: 64}
     * ```
     */
    toChunk() {
        const origin = new Vector3(this);
        const modX = origin.x % 16;
        const modZ = origin.z % 16;
        if (modX === 0)
            origin.x += origin.x < 0 ? 16 : 0;
        if (modZ === 0)
            origin.z += origin.z < 0 ? 16 : 0;
        const x = Math.floor(origin.x < 0 ? origin.x - (16 + modX) : origin.x - modX);
        const z = Math.floor(origin.z < 0 ? origin.z - (16 + modZ) : origin.z - modZ);
        return new Vector3({ x, y: 0, z });
    }
    /**
     * @remarks Gets the local offset from this vector, akin to the `^ ^ ^` notation in commands.
     * @param forward The forward vector for the conversion. For entities this is the view direction.
     * @param offset The offset local to the origin, i.e. the `^ ^ ^`.
     * @returns The local offset from this vector.
     */
    toLocal(forward, offset) {
        offset.x = -offset.x;
        const right = forward.cross(Vector3.UP).normalize();
        const up = forward.cross(right).normalize();
        const local = new Vector3({
            x: offset.z * forward.x + offset.x * right.x + offset.y * up.x,
            y: offset.z * forward.y + offset.x * right.y + offset.y * up.y,
            z: offset.z * forward.z + offset.x * right.z + offset.y * up.z,
        });
        return this.add(local);
    }
    /**
     * @remarks Represents a Vector3 as spaced out numbers, can be used in commands.
     * @returns A string representation of the Vector3.
     * @example
     * ```typescript
     * const player = getAllPlayersSafe()[0];
     * player.runCommand(`setblock ${location.toString()} stone`); // toString can be called manually.
     * player.runCommand(`setblock ${location} stone`); // toString is also automatically called when using string conversion.
     * ```
     */
    toString() {
        return `${this.x} ${this.y} ${this.z}`;
    }
    /**
     * @deprecated Result moved to toString to allow default string conversion in the command format
     * Converts a vector to a string used for command strings.
     * @returns A string formated for command inputs.
     */
    toCommandString() {
        return `${this.x} ${this.y} ${this.z}`;
    }
}
Vector3.ZERO = new Vector3({ x: 0, y: 0, z: 0 });
Vector3.UP = new Vector3({ x: 0, y: 1, z: 0 });
Vector3.DOWN = new Vector3({ x: 0, y: -1, z: 0 });
Vector3.NORTH = new Vector3({ x: 0, y: 0, z: -1 });
Vector3.SOUTH = new Vector3({ x: 0, y: 0, z: 1 });
Vector3.EAST = new Vector3({ x: 1, y: 0, z: 0 });
Vector3.WEST = new Vector3({ x: -1, y: 0, z: 0 });
/**
 * @remarks A class to represent an axis aligned bounding box.
 */
export class AABB {
    /**
     * @remarks Creates a new axis aligned bounding box.
     * @param min The negative-most coordinate of the box.
     * @param max The positive-most coordinate of the box.
     */
    constructor(min, max) {
        this.min = min;
        this.max = max;
    }
    /**
     * @remarks Checks if a point is within the AABB.
     * @param vector The point to check against.
     * @returns True if the point is within the box, false otherwise.
     */
    contains(vector) {
        return (vector.x >= this.min.x && vector.x <= this.max.x &&
            vector.y >= this.min.y && vector.y <= this.max.y &&
            vector.z >= this.min.z && vector.z <= this.max.z);
    }
    /**
     * @remarsks Checks if this AABB intersects with another AABB.
     * @param other The other AABB to check against.
     * @returns True if this AABB intersects with the other AABB, false otherwise.
     */
    intersects(other) {
        return (this.min.x <= other.max.x && this.max.x >= other.min.x &&
            this.min.y <= other.max.y && this.max.y >= other.min.y &&
            this.min.z <= other.max.z && this.max.z >= other.min.z);
    }
    /**
     * @remarks Checks if a ray (a line represented by an origin, direction, and distance) will intersect with this box.
     * @param dimension The dimension the raycast should be performed in.
     * @param origin The origin of the raycast.
     * @param direction The direction of the raycast.
     * @param options Block options that will be used to determine the raycasts direction and any block hit information that would interrupt the raycast.
     * @returns True if the ray intersects the box, false otherwise.
     */
    rayIntersects(dimension, origin, direction, options) {
        var _a;
        const distance = (_a = options.maxDistance) !== null && _a !== void 0 ? _a : 1;
        const lineEnd = origin.add(direction.multiply(distance));
        const dir = lineEnd.subtract(origin);
        const invDir = {
            x: 1 / dir.x,
            y: 1 / dir.y,
            z: 1 / dir.z,
        };
        const tMin = {
            x: (this.min.x - origin.x) * invDir.x,
            y: (this.min.y - origin.y) * invDir.y,
            z: (this.min.z - origin.z) * invDir.z,
        };
        const tMax = {
            x: (this.max.x - origin.x) * invDir.x,
            y: (this.max.y - origin.y) * invDir.y,
            z: (this.max.z - origin.z) * invDir.z,
        };
        const t1 = {
            x: Math.min(tMin.x, tMax.x),
            y: Math.min(tMin.y, tMax.y),
            z: Math.min(tMin.z, tMax.z),
        };
        const t2 = {
            x: Math.max(tMin.x, tMax.x),
            y: Math.max(tMin.y, tMax.y),
            z: Math.max(tMin.z, tMax.z),
        };
        const tNear = Math.max(t1.x, t1.y, t1.z);
        const tFar = Math.min(t2.x, t2.y, t2.z);
        const intersects = tNear <= tFar && tFar >= 0 && tNear <= 1;
        const distanceResult = lerp(0, distance, intersects ? Math.max(0, tNear) : 0);
        return intersects && !dimension.getBlockFromRay(origin, direction, { maxDistance: distanceResult + 0.45 });
    }
}
