/* Module "vector.wren" */
/* Vector represents a two dimensional geometric vector in the plane. */
class Vector {
// Gets or sets the default angle unit.
static useDegrees { __useDeg }
static useDegrees=(v) { (v is Bool) ? __useDeg = v : Fiber.abort("Invalid argument.") }
// Returns a zero Vector.
static zero { Vector.new(0, 0) }
// Returns v1 * v2 or v2 * v1 depending on which order the arguments are presented.
static scale(v1, v2) {
if ((v1 is Vector) && (v2 is Num)) return v1 * v2
if ((v1 is Num) && (v2 is Vector)) return v2 * v1
Fiber.abort("One argument must be a Vector and the other a number.")
}
// Efficiently sums a list of vectors.
static sumAll(vectors) {
if (!(vectors is List) || vectors.count == 0 || !(vectors[0] is Vector)) {
Fiber.abort("Argument must be a non-empty list of vectors.")
}
if (vectors.count == 1) return vectors[0].copy()
if (vectors.count == 2) return vectors[0] + vectors[1]
var sx = vectors.map { |v| v.x }.reduce { |acc, x| acc + x }
var sy = vectors.map { |v| v.y }.reduce { |acc, y| acc + y }
return Vector.new(sx, sy)
}
// Constructs a Vector from polar coordinates.
static fromPolar(r, theta) {
if (!(r is Num) || !(theta is Num)) Fiber.abort("Arguments must both be numbers.")
if (__useDeg) theta = theta / 180 * Num.pi
return new(r * theta.cos, r * theta.sin)
}
// Constructs a Vector from cartesian coordinates.
construct new(x, y) {
if (!(x is Num) || !(y is Num)) Fiber.abort("Arguments must both be numbers.")
_x = x
_y = y
}
// Self-evident properties.
x { _x }
y { _y }
x=(v) { _x = v }
y=(v) { _y = v }
square { _x * _x + _y * _y }
radius { square.sqrt }
length { radius }
manhattan { _x.abs + _y.abs }
theta {
var v = _y.atan(_x)
if (v < 0) v = v + Num.tau // ensure in [0, 2π]
if (__useDeg) v = v * 180 / Num.pi
return v
}
// Returns a Vector of the same length which is perpendicular to this one.
perp { Vector.new(-_y, x) }
// Returns a unit Vector with the same direction as this one.
unit {
if (length == 0) return Vector.new(0, 0)
return Vector.new(_x/length, _y/length)
}
// Basic operations.
-{ Vector.new(-_x, -_y) }
-(other) { this + (-other) }
+(other) {
if (!(other is Vector)) Fiber.abort("Other must be a Vector.")
return Vector.new(_x + other.x, _y + other.y)
}
*(n) {
if (!(n is Num)) Fiber.abort("n must be a number.")
return Vector.new(_x * n, _y * n)
}
/(n) {
if (!(n is Num)) Fiber.abort("n must be a number.")
return Vector.new(_x / n, _y / n)
}
==(other) {
if (!(other is Vector)) Fiber.abort("Other must be a Vector.")
return x == other.x && _y == other.y
}
!=(other) { !(this == other) }
// Returns the dot product of this and another Vector.
dot(other) {
if (!(other is Vector)) Fiber.abort("Other must be a Vector.")
return _x * other.x + _y * other.y
}
// Returns whether or not this Vector is perpendicular to another one.
isPerpTo(other) {
if (!(other is Vector)) Fiber.abort("Other must be a Vector.")
return this.dot(other) == 0
}
// Returns the angle between this Vector and another one.
angleBetween(other) {
if (!(other is Vector)) Fiber.abort("Other must be a Vector.")
var v = (this.dot(other)/(length * other.length)).acos
if (__useDeg) v = v * 180 / Num.pi
return v
}
// Returns the distance between this and another Vector.
dist(other) {
if (!(other is Vector)) Fiber.abort("Other must be a Vector.")
var d1 = _x - other.x
var d2 = _y - other.y
return (d1 * d1 + d2 * d2).sqrt
}
// Returns a copy of this Vector.
copy() { Vector.new(_x, _y) }
// Returns the cartesian coordinates of this Vector as a list.
toCartesian { [_x, _y] }
// Returns the polar coordinates of this Vector as a list.
toPolar { !__useDeg ? [radius, theta] : [radius, theta * 180 / Num.pi] }
// Returns a string representation of this Vector in cartesian coordinates.
toString { "(%(_x), %(_y))" }
}
/* Vector3 represents a three dimensional geometric vector in space. */
class Vector3 {
// Gets or sets the default angle unit.
static useDegrees { __useDeg }
static useDegrees=(v) { (v is Bool) ? __useDeg = v : Fiber.abort("Invalid argument.") }
// Returns a zero Vector3.
static zero { Vector3.new(0, 0, 0) }
// Returns v1 * v2 or v2 * v1 depending on which order the arguments are presented.
static scale(v1, v2) {
if ((v1 is Vector3) && (v2 is Num)) return v1 * v2
if ((v1 is Num) && (v2 is Vector3)) return v2 * v1
Fiber.abort("One argument must be a Vector3 and the other a number.")
}
// Efficiently sums a list of vector3s.
static sumAll(vector3s) {
if (!(vector3s is List) || vector3s.count == 0 || !(vector3s[0] is Vector3)) {
Fiber.abort("Argument must be a non-empty list of vector3s.")
}
if (vector3s.count == 1) return vector3s[0].copy()
if (vector3s.count == 2) return vector3s[0] + vector3s[1]
var sx = vector3s.map { |v| v.x }.reduce { |acc, x| acc + x }
var sy = vector3s.map { |v| v.y }.reduce { |acc, y| acc + y }
var sz = vector3s.map { |v| v.z }.reduce { |acc, z| acc + z }
return Vector3.new(sx, sy, sz)
}
// Constructs a Vector3 from cylindrical coordinates.
static fromCylindrical(r, theta, z) {
if (!(r is Num) || !(theta is Num) || !(z is Num)) {
Fiber.abort("Arguments must all be numbers.")
}
if (__useDeg) theta = theta / 180 * Num.pi
return new(r * theta.cos, r * theta.sin, z)
}
// Constructs a Vector3 from spherical coordinates.
static fromSpherical(r, theta, phi) {
if (!(r is Num) || !(theta is Num) || !(phi is Num)) {
Fiber.abort("Arguments must all be numbers.")
}
if (__useDeg) {
theta = theta / 180 * Num.pi
phi = phi / 180 * Num.pi
}
return new(r * theta.cos * phi.sin, r * theta.sin * phi.sin, r * phi.cos)
}
// Constructs a Vector3 from cartesian coordinates.
construct new(x, y, z) {
if (!(x is Num) || !(y is Num) || !(z is Num)) {
Fiber.abort("Arguments must all be numbers.")
}
_x = x
_y = y
_z = z
}
// Self-evident properties.
x { _x }
y { _y }
z { _z }
x=(v) { _x = v }
y=(v) { _y = v }
z=(v) { _z = v }
square { _x * _x + _y * _y + _z * _z }
length { square.sqrt }
radius { (_x * _x + _y * _y).sqrt }
manhattan { _x.abs + _y.abs + _z.abs }
theta {
var v = _y.atan(_x)
if (v < 0) v = v + Num.tau // ensure in [0, 2π]
if (__useDeg) v = v * 180 / Num.pi
return v
}
phi {
if (length == 0) return 0
var v = (_z / length).acos
if (__useDeg) v = v * 180 / Num.pi
return v
}
// Returns a unit Vector with the same direction as this one.
unit {
if (length == 0) return Vector3.new(0, 0, 0)
return Vector3.new(_x/length, _y/length, _z/length)
}
// Basic operations.
-{ Vector3.new(-_x, -_y, -_z) }
+(other) {
if (!(other is Vector3)) Fiber.abort("Other must be a Vector3.")
return Vector3.new(_x + other.x, _y + other.y, _z + other.z)
}
-(other) { this + (-other) }
*(n) {
if (!(n is Num)) Fiber.abort("n must be a number.")
return Vector3.new(_x * n, _y * n, _z * n)
}
/(n) {
if (!(n is Num)) Fiber.abort("n must be a number.")
return Vector3.new(_x / n, _y / n, _z / n)
}
==(other) {
if (!(other is Vector3)) Fiber.abort("Other must be a Vector3.")
return x == other.x && _y == other.y && _z == other.z
}
!=(other) { !(this == other) }
// Returns the dot product of this and another Vector3.
dot(other) {
if (!(other is Vector3)) Fiber.abort("Other must be a Vector3.")
return _x * other.x + _y * other.y + _z * otherz
}
// Returns the cross product of this and another Vector3.
cross(other) {
if (!(other is Vector3)) Fiber.abort("Other must be a Vector3.")
return Vector3.new(
_y * other.z - _z * other.y,
_z * other.x - _x * other.z,
_x * other.y - _y * other.x
)
}
// Returns the scalar triple product of this and two other Vector3s.
scalarTripleProd(other1, other2) {
if (!(other1 is Vector3) || !(other2 is Vector3)) {
Fiber.abort("Arguments must both be Vector3s.")
}
return this.dot(other1.cross(other2))
}
// Returns the vector triple product of this and two other Vector3s.
vectorTripleProd(other1, other2) {
if (!(other1 is Vector3) || !(other2 is Vector3)) {
Fiber.abort("Arguments must both be Vector3s.")
}
return this.cross(other1.cross(other2))
}
// Returns the scalar quadruple product of this and three other Vector3s.
scalarQuodProd(other1, other2, other3) {
if (!(other1 is Vector3) || !(other2 is Vector3) || !(other3 is Vector3)) {
Fiber.abort("Arguments must all be Vector3s.")
}
return this.cross(other1).dot(other2.cross(other3))
}
// Returns the vector quadruple product of this and three other Vector3s.
vectorQuodProd(other1, other2, other3) {
if (!(other1 is Vector3) || !(other2 is Vector3) || !(other3 is Vector3)) {
Fiber.abort("Arguments must all be Vector3s.")
}
return this.cross(other1).cross(other2.cross(other3))
}
// Returns the angle between this Vector3 and another one.
angleBetween(other) {
if (!(other is Vector3)) Fiber.abort("Other must be a Vector3.")
var v = (this.dot(other)/(length * other.length)).acos
if (__useDeg) v = v * 180 / Num.pi
return v
}
// Returns the distance between this and another Vector3.
dist(other) {
if (!(other is Vector)) Fiber.abort("Other must be a Vector3.")
var d1 = _x - other.x
var d2 = _y - other.y
var d3 = _z - other.z
return (d1 * d1 + d2 * d2 + d3 * d3).sqrt
}
// Returns a copy of this Vector3.
copy() { Vector3.new(_x, _y, _z) }
// Returns the cartesian coordinates of this Vector3 as a list.
toCartesian { [_x, _y, _z] }
// Returns the cylindrical coordinates of this Vector3 as a list.
toCylindrical { !__useDeg ? [radius, theta, _z] : [radius, theta * 180 / Num.pi , _z]}
// Returns the spherical coordinates of this Vector3 as a list.
toSpherical { !__useDeg ? [length, theta, phi] : [length, theta * 180 /Num.pi, phi * 180 / Num.pi] }
// Returns a string representation of this Vector3 in cartesian coordinates.
toString { "(%(_x), %(_y), %(_z))" }
}
// Set initial angle unit defaults (radians).
Vector.useDegrees = false
Vector3.useDegrees = false
var Vector2 = Vector // alias for Vector class