MineJSv0.5.1
physics/direction.tstypescript
export interface BaseVec3 {
    x: number,
    y: number,
    z: number
}

/**
 * Represent a vector which has 3 coords
 */
export class Vec3 implements BaseVec3 {
    public static readonly Zero = new Vec3(0, 0, 0);
    public static readonly XAxis = new Vec3(1, 0, 0);
    public static readonly YAxis = new Vec3(0, 1, 0);
    public static readonly ZAxis = new Vec3(0, 0, 1);

    /**
     * Helper function for loading vec3 args
     */
    static loadArgs(a: BaseVec3 | number, b?: number, c?: number): BaseVec3 {
        let x = 1, y = 1, z = 1;
        if (typeof a === "object" && "x" in a) {
            x = a.x;
            y = a.y;
            z = a.z;
        } else if (a !== undefined && b !== undefined && c !== undefined) {
            x = a;
            y = b;
            z = c;
        }
        return { x, y, z };
    }

    public x: number;
    public y: number;
    public z: number;

    constructor(vec3: BaseVec3)
    constructor(
        x: number,
        y: number,
        z: number
    )
    constructor(
        a: number | BaseVec3,
        b?: number,
        c?: number
    ) {
        const { x, y, z } = Vec3.loadArgs(a, b, c);
        this.x = x;
        this.y = y;
        this.z = z;
    }

    /**
     * Copy this object
     */
    public copy() {
        return new Vec3(this.copyBase());
    }
    /**
     * Copy but just return the base value, not the whole instance
     */
    public copyBase(): BaseVec3 {
        return {
            x: this.x,
            y: this.y,
            z: this.z,
        };
    }

    public normalize() {
        const d = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
        return d < 1.0e-5 ? Vec3.Zero : new Vec3(this.x / d, this.y / d, this.z / d);
    }

    public dot(x: number, y: number, z: number): number;
    public dot(vec: BaseVec3): number;
    public dot(a: BaseVec3 | number, b?: number, c?: number): number {
        const vec = Vec3.loadArgs(a, b, c);
        return this.x * vec.x +
            this.y * vec.y +
            this.z * vec.z;
    }


    public cross(x: number, y: number, z: number): Vec3;
    public cross(vec: BaseVec3): Vec3;
    public cross(a: BaseVec3 | number, b?: number, c?: number): Vec3 {
        const vec = Vec3.loadArgs(a, b, c);
        return new Vec3(this.y * vec.z - this.z * vec.y, this.z * vec.x - this.x * vec.z, this.x * vec.y - this.y * vec.x);
    }

    public add(x: number, y: number, z: number): Vec3;
    public add(vec: BaseVec3): Vec3;
    public add(a: BaseVec3 | number, b?: number, c?: number) {
        const vec = Vec3.loadArgs(a, b, c);
        const x = this.x + vec.x,
            y = this.y + vec.y,
            z = this.z + vec.z;
        return new Vec3(x, y, z);
    }

    public subtract(x: number, y: number, z: number): Vec3;
    public subtract(vec: BaseVec3): Vec3;
    public subtract(a: BaseVec3 | number, b?: number, c?: number) {
        const vec = Vec3.loadArgs(a, b, c);
        const x = this.x - vec.x,
            y = this.y - vec.y,
            z = this.z - vec.z;
        return new Vec3(x, y, z);
    }

    public multiply(a: number | BaseVec3): Vec3;
    public multiply(x: number, y: number, z: number): Vec3;
    public multiply(a: BaseVec3 | number, b?: number, c?: number) {
        let vec: BaseVec3;
        if ((a && b && c) || (typeof a === "object" && "x" in a)) vec = Vec3.loadArgs(a, b, c);
        else vec = { x: a, y: a, z: a };
        const x = this.x * vec.x;
        const y = this.y * vec.y;
        const z = this.z * vec.z;
        return new Vec3(x, y, z);
    }

    public distanceTo(x: number, y: number, z: number): number;
    public distanceTo(vec: BaseVec3): number;
    public distanceTo(a: BaseVec3 | number, b?: number, c?: number): number {
        const vec = Vec3.loadArgs(a, b, c);
        const d = vec.x - this.x;
        const e = vec.y - this.y;
        const f = vec.z - this.z;
        return Math.sqrt(d * d + e * e + f * f);
    }

    public distanceToSqr(x: number, y: number, z: number): number;
    public distanceToSqr(vec: BaseVec3): number;
    public distanceToSqr(a: BaseVec3 | number, b?: number, c?: number): number {
        const vec = Vec3.loadArgs(a, b, c);
        const d = vec.x - this.x;
        const e = vec.y - this.y;
        const f = vec.z - this.z;
        return d * d + e * e + f * f;
    }

    public length() {
        return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
    }
    public lengthSqr() {
        return this.x * this.x + this.y * this.y + this.z * this.z;
    }

    public equal(x: number, y: number, z: number): boolean;
    public equal(vec: BaseVec3): boolean;
    public equal(a: BaseVec3 | number, b?: number, c?: number): boolean {
        const vec = Vec3.loadArgs(a, b, c);
        return this.x === vec.x &&
            this.y === vec.y &&
            this.z === vec.z;
    }

    /**
     * Create new Vec3 with one updated value, keep the rest.
     * @param axis 
     * @param value 
     */
    public with(axis: BaseAxis, value: number) {
        const x = axis === BaseAxis.X ? value : this.x;
        const y = axis === BaseAxis.Y ? value : this.y;
        const z = axis === BaseAxis.Z ? value : this.z;
        return new Vec3(x, y, z);
    }

    /**
     * Get value of an axis
     * @param axis 
     * @returns 
     */
    public get(axis: BaseAxis) {
        return Axis.choose(axis,
            this.x,
            this.y,
            this.z,
        );
    }

    public horizontalDistance() {
        return Math.sqrt((this.x * this.x) + (this.z * this.z));
    }
    public horizontalDistanceSqr() {
        return (this.x * this.x) + (this.z * this.z);
    }

    public scale(val: number) {
        return this.multiply(val);
    }

    public isFinite() {
        return isFinite(this.x) && isFinite(this.y) && isFinite(this.z);
    }
}

export enum BaseAxis {
    X,
    Y,
    Z
}

/** @hidden */
export class Axis {
    /**
     * Helper function to return value corresponding to given axis
     */
    public static choose<T>(axis: BaseAxis, x: T, y: T, z: T): T {
        switch (axis) {
            case BaseAxis.X: return x;
            case BaseAxis.Y: return y;
            case BaseAxis.Z: return z;
        }
    }

    /**
     * Collision math
     */
    public static stepOrder(x: number, z: number) {
        if (Math.abs(x) < Math.abs(z)) return [BaseAxis.Y, BaseAxis.Z, BaseAxis.X];
        return [BaseAxis.Y, BaseAxis.X, BaseAxis.Z];
    }
}

/**
 * For collision math.
 * 
 * This is for "rotating" the Axis
 * 
 * @hidden
 */
export abstract class AxisCycle {
    /**
     * Don't rotate the Axis
     */
    public static NONE: AxisCycle = new class extends AxisCycle {
        public cycle(axis: BaseAxis): BaseAxis {
            return axis;
        }
        public cycleCoords<T>(x: T, y: T, z: T, axis: BaseAxis): T {
            return Axis.choose(axis, x, y, z);
        }
        public inverse(): AxisCycle {
            return this;
        }
    };
    /**
     * Move forward 1 unit
     */
    public static FORWARD: AxisCycle = new class extends AxisCycle {
        public cycle(axis: BaseAxis): BaseAxis {
            return AxisCycle.Axises[(axis + 1) % 3]!;
        }
        public cycleCoords<T>(x: T, y: T, z: T, axis: BaseAxis): T {
            return Axis.choose(axis, z, x, y);
        }
        public inverse(): AxisCycle {
            return AxisCycle.BACKWARD;
        }
    };
    /**
     * Move forward 2 units, or backward 1 unit
     * 
     * For the name explaination, in a cyclic three-value array, move forward 2 steps equals step backward 1 step.
     * 
     * Example:
     * ```
     * You are here
     *  v
     * [0, 1, 2]
     * ```
     * Now move two:
     * ```
     * Now you are here
     *        v
     * [0, 1, 2]
     * ```
     * Thats equal to, step one backward:
     * ```
     * you still here
     *        v
     * [0, 1, 2]
     * ```
     */
    public static BACKWARD: AxisCycle = new class extends AxisCycle {
        public cycle(axis: BaseAxis): BaseAxis {
            return AxisCycle.Axises[(axis - 1 + 3) % 3]!;
        }
        public cycleCoords<T>(x: T, y: T, z: T, axis: BaseAxis): T {
            return Axis.choose(axis, y, z, x);
        }
        public inverse(): AxisCycle {
            return AxisCycle.FORWARD;
        }
    };

    public static Values: AxisCycle[] = [this.NONE, this.FORWARD, this.BACKWARD];
    public static Axises: BaseAxis[] = [BaseAxis.X, BaseAxis.Y, BaseAxis.Z];

    public abstract cycle(axis: BaseAxis): BaseAxis;
    public abstract cycleCoords<T>(x: T, y: T, z: T, axis: BaseAxis): T;
    public abstract inverse(): AxisCycle;

    public static between(from: BaseAxis, to: BaseAxis): AxisCycle {
        return this.Values[(to - from + 3) % 3]!;
    }

    public static readonly isAxis = true;
}

/**
 * For clipping math
 * 
 * @hidden
 */
export class Direction {
    public static readonly DOWN = new Direction(0, BaseAxis.Y, new Vec3(0, -1, 0));
    public static readonly UP = new Direction(1, BaseAxis.Y, new Vec3(0, 1, 0));
    public static readonly NORTH = new Direction(2, BaseAxis.Z, new Vec3(0, 0, -1));
    public static readonly SOUTH = new Direction(3, BaseAxis.Z, new Vec3(0, 0, 1));
    public static readonly WEST = new Direction(4, BaseAxis.X, new Vec3(-1, 0, 0));
    public static readonly EAST = new Direction(5, BaseAxis.X, new Vec3(1, 0, 0));
    public static readonly Values = [this.DOWN, this.UP, this.NORTH, this.SOUTH, this.WEST, this.EAST];

    constructor(
        public index3D: number,
        public axis: BaseAxis,
        public normal: Vec3
    ) { }

    public static getApproximateNearest(x: number, y: number, z: number): Direction;
    public static getApproximateNearest(vec: BaseVec3): Direction;
    public static getApproximateNearest(a: BaseVec3 | number, b?: number, c?: number): Direction {
        const { x: dx, y: dy, z: dz } = Vec3.loadArgs(a, b, c);
        let result = this.NORTH;
        let highestDot = Number.MIN_VALUE;
        for (const direction of this.Values) {
            const dot = (dx * direction.normal.x) + (dy * direction.normal.y) + (dz * direction.normal.z);
            if (dot > highestDot) {
                highestDot = dot;
                result = direction;
            }
        }
        return result;
    }
}

export interface Angle {
    yaw: number,
    pitch: number
}