UNPKG

@casual-simulation/aux-common

Version:
398 lines 15.1 kB
/* CasualOS is a set of web-based tools designed to facilitate the creation of real-time, multi-user, context-aware interactive experiences. * * Copyright (c) 2019-2025 Casual Simulation, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ import { UNCOPIABLE } from '@casual-simulation/js-interpreter/InterpreterUtils'; import { clamp } from '../utils'; import { Vector2 } from './Vector2'; /** * Defines a class that represents a 3D point in space. * @dochash types/math/vectors * @docorder 1 * @doctitle Vectors * @docsidebar Vectors * @docdescription Vectors help represent positions and directions. * @docname Vector3 */ export class Vector3 { /** * Gets a new Vector2 that contains this vector's X and Y components. */ get xy() { return new Vector2(this.x, this.y); } /** * Gets a new Vector2 that contains this vector's X and Z components. */ get xz() { return new Vector2(this.x, this.z); } /** * Gets a new Vector2 that contains this vector's Y and Z components. */ get yz() { return new Vector2(this.y, this.z); } /** * Constructs a new 3D vector with the given X and Y values. * @param x The X value of the vector. * @param y The Y value of the vector. * @param z The Z value of the vector. * * @example Create a new Vector3 object with the position (2, 3, 4). * let myVector = new Vector3(2, 3, 4); * * os.toast(`X: ${myVector.x}, Y: ${myVector.y}, Z: ${myVector.z}`); * * @example Move this bot to (1, 2, 3) in the home dimension. * tags.homePosition = new Vector3(1, 2, 3); */ constructor(x = 0, y = 0, z = 0) { Object.defineProperty(this, UNCOPIABLE, { value: true, writable: false, enumerable: false, }); this.x = x; this.y = y; this.z = z; } /** * Creates a 3D vector with the given X and Y values that is normalized immediately upon creation. * @param x The X value of the vector. * @param y The Y value of the vector. * @param z The Z value of the vector. * * @example Create a normalized vector * const vector = Vector3.createNormalized(1, 2, 3); */ static createNormalized(x, y, z) { const length = Math.sqrt(x * x + y * y + z * z); return new Vector3(x / length, y / length, z / length); } /** * Calculates the angle between the two given vectors and returns the result in radians. * @param first The first vector that should be used for comparision. * @param second The second vector that should be used for comparision. * * @example Find the angle between two vectors. * const first = new Vector3( * Math.cos(Math.PI / 3), * Math.sin(Math.PI / 3), * 0, * ); // 60 degrees * const second = new Vector3( * Math.cos(Math.PI / 2), * Math.sin(Math.PI / 2), * 0 * ); // 90 degrees * * const angle = Vector3.angleBetween(first, second); * os.toast(angle); */ static angleBetween(first, second) { const dot = first.dot(second); const l1 = first.length(); const l2 = second.length(); const cos = dot / (l1 * l2); if (cos <= 1 && cos >= -1) { return Math.acos(cos); } else { // Sometimes the dot product ends up outside the 1 <-> -1 range and we need to clamp it. return Math.acos(clamp(cos, -1, 1)); } } /** * Calculates the distance between the two given vectors and returns the result. * @param first The first vector that should be used for comparision. * @param second The second vector that should be used for comparision. * * @example Find the distance between two vectors. * const first = new Vector3(5, 10, 3); * const second = new Vector3(9, 2, 6); * const distance = Vector3.distanceBetween(first, second); * * os.toast(`Distance: ${distance}`); */ static distanceBetween(first, second) { const direction = second.subtract(first); return direction.length(); } /** * Constructs a new vector that is the linear interpolation between the given start and end positions. * The degree that the result is interpolated is determined by the given amount parameter. * @param start The start position. * @param finish The end position. * @param amount The amount that the resulting position should be interpolated between the start and end positions. Values near 0 indicate rotations close to the first and values near 1 indicate rotations close to the second. * * @example Find the position that is halfway between two vectors. * const start = new Vector3(5, 10, 15); * const finish = new Vector3(9, 2, 6); * const halfway = Vector3.interpolatePosition(start, finish, 0.5); * * os.toast(halfway); * * @example Find the position that is 1/4 between two vectors. * const start = new Vector3(5, 10, 15); * const finish = new Vector3(9, 2, 6); * const halfway = Vector3.interpolatePosition(start, finish, 0.25); * * os.toast(halfway); */ static interpolatePosition(start, finish, amount) { const dir = finish.subtract(start); const lerp = dir.multiplyScalar(amount); return start.add(lerp); } /** * Constructs a new vector that is the directional linear interpolation between the given start and end positions. * The degree that the result is interpolated is determined by the given amount parameter. * * This function works similarly to interpolatePosition(), except the result is always a normalized vector. * * @param start The start position. * @param finish The end position. * @param amount The amount that the resulting position should be interpolated between the start and end positions. Values near 0 indicate rotations close to the first and values near 1 indicate rotations close to the second. * * @example Find the direction that points halfway between the two vectors. * const start = new Vector3(5, 10, 16); * const finish = new Vector3(9, 2, 6); * const halfway = Vector3.interpolatePosition(start, finish, 0.5); * * os.toast(halfway); */ static interpolateDirection(start, finish, amount) { return Vector3.interpolatePosition(start, finish, amount).normalize(); } /** * Adds this vector with the other vector and returns the result. * @param other The other vector to add with this vector. * * @example Add two vectors together. * const first = new Vector3(1, 2, 3); * const second = new Vector3(3, 4, 5); * const added = first.add(second); * * os.toast(added); // Prints (4, 6, 8) */ add(other) { return new Vector3(this.x + other.x, this.y + other.y, this.z + other.z); } /** * Subtracts the other vector from this vector and returns the result. * @param other The other vector that should be subtracted from this vector. * * @example Subtract two vectors. * const first = new Vector3(1, 2, 3); * const second = new Vector3(3, 4, 5); * const subtracted = first.subtract(second); * os.toast(subtracted); * * @example Find the direction from one vector to another. * const first = new Vector3(1, 2, 3); * const second = new Vector3(3, 4, 5); * * const directionFromFirstToSecond = second.subtract(first); * const directionFromSecondToFirst = first.subtract(second); * * os.toast(`first -> second = ${directionFromFirstToSecond}; second -> first = ${directionFromSecondToFirst}`); */ subtract(other) { return new Vector3(this.x - other.x, this.y - other.y, this.z - other.z); } /** * Multiplies each component of this vector by the given value and returns the result. * @param scale The scale that should be applied to this vector. * * @example Scale a vector by 10. * const myVector = new Vector3(1, 1, 1); * const scaled = myVector.multiplyScalar(10); * os.toast(scaled); // Prints (10, 10, 10) */ multiplyScalar(scale) { return new Vector3(this.x * scale, this.y * scale, this.z * scale); } /** * Multiplies this vector by the given other vector and returns the result. * @param other The other vector to multiply with this vector. * * @example Multiply two vectors together. * const first = new Vector3(1, 2, 3); * const second = new Vector3(3, 4, 5); * const multiplied = first.multiply(second); * * os.toast(multiplied); // Prints (3, 8, 15) */ multiply(other) { return new Vector3(this.x * other.x, this.y * other.y, this.z * other.z); } /** * Calculates the dot product of this vector compared to the given other vector. * Returns a number that is positive if the vectors point in the same direction, * negative if they point in opposite directions, and zero if they are perpendicular. * For normalized vectors, this value is clamped to 1 and -1. * @param other The other vector to calculate the dot product with. * * @example Determine how two vectors are pointing towards/away from the same direction. * const first = new Vector3(1, 2, 3); * const second = new Vector3(3, 4, 5); * * const dot = first.dot(second); * if (dot < 0) { * os.toast("Vectors are pointing away from each other!"); * } else if (dot === 0) { * os.toast("Vectors 90 degrees away from each other!"); * } else { * os.toast("Vectors are pointing towards from each other!"); * } */ dot(other) { return this.x * other.x + this.y * other.y + this.z * other.z; } /** * Calculates the cross product of this vector with the given other vector. * Returns a new vector that is perpendicular to both vectors. * Note that the order of the vectors greatly matters. For example, (1, 0, 0).cross(0, 1, 0) === (0, 0, 1) but (0, 1, 0).cross(1, 0, 0) === (0, 0, -1). * @param other The other vector to calculate the cross product with. * * @example Calculate a vector that is perpendicular to two vectors. * const first = new Vector3(1, 0, 0); * const second = new Vector3(0, 1, 0); * * const result = first.cross(second); * os.toast(`Result: ${result}`); // Prints (0, 0, 1) */ cross(other) { return new Vector3(this.y * other.z - other.y * this.z, this.z * other.x - other.z * this.x, this.x * other.y - other.x * this.y); } /** * Calculates the length of this vector and returns the result. * * @example Get the length of the vector. * const myVector = new Vector3(1, 2, 3); * const length = myVector.length(); * * os.toast(`Vector is ${length} units long`); */ length() { return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); } /** * Calculates the square length of this vector and returns the result. * This is equivalent to length^2, but it is faster to calculate than length because it doesn't require * calculating a square root. * * @example Get the square length of the vector. * const myVector = new Vector3(1, 2, 3); * const length = myVector.squareLength(); * * os.toast(`Vector is ${length}^2 units long`); */ squareLength() { return this.x * this.x + this.y * this.y + this.z * this.z; } /** * Calculates the normalized version of this vector and returns it. * A normalized vector is a vector whose length equals 1. * * Normalizing a vector preserves its directionality while making the length (i.e. scale) of it 1. * * @example Normalize a vector. * const myVector = new Vector3(1, 2, 3); * const normalized = myVector.normalize(); * * os.toast(`Vector: ${myVector}, Normalized: ${normalized}`); */ normalize() { const length = this.length(); if (length === 1) { return this; } return new Vector3(this.x / length, this.y / length, this.z / length); } /** * Negates each component of this vector and returns a new vector that contains the result. * * @example Negate a vector. * const myVector = new Vector3(1, 2, 3); * const negated = myVector.negate(); * * os.toast(`Vector: ${myVector}, Negated: ${negated}`); */ negate() { return new Vector3(-this.x, -this.y, -this.z); } /** * Converts this vector to a human-readable string representation. * * @example Get a string of a vector. * const myVector = new Vector3(1, 2, 3); * const vectorString = myVector.toString(); * * os.toast('My Vector: ' + vectorString); */ toString() { return `Vector3(${this.x}, ${this.y}, ${this.z})`; } /** * Determines if this vector equals the other vector. * @param other The other value to compare to. * * @example Determine if two vectors represent the same value. * const first = new Vector3(1, 2, 3); * const second = new Vector3(3, 4, 5); * const third = new Vector3(1, 2, 3); * * os.toast(`first == second: ${first.equals(second)}; first == third: ${first.equals(third)}`) */ equals(other) { return this.x === other.x && this.y === other.y && this.z === other.z; } } /** * A 3D vector that contains (0, 0, 0). */ export const ZERO = new Vector3(); /** * A 3D vector that contains (1, 1, 1). */ export const ONE = new Vector3(1, 1, 1); /** * A 3D vector that contains (0, 1, 0). */ export const FORWARD = new Vector3(0, 1, 0); /** * A 3D vector that contains (0, -1, 0). */ export const BACK = new Vector3(0, -1, 0); /** * A 3D vector that contains (1, 0, 0). */ export const RIGHT = new Vector3(1, 0, 0); /** * A 3D vector that contains (-1, 0, 0). */ export const LEFT = new Vector3(-1, 0, 0); /** * A 3D vector that contains (0, 0, 1). */ export const UP = new Vector3(0, 0, 1); /** * A 3D vector that contains (0, 0, -1). */ export const DOWN = new Vector3(0, 0, -1); //# sourceMappingURL=Vector3.js.map