Category talk:Wren-vector: Difference between revisions
Content added Content deleted
(→Source code: Bug fix.) |
(→Source code: Numerous changes most notably renaming Vector class to Vector2 and adding a generic n-dimensional Vector class.) |
||
Line 3: | Line 3: | ||
<syntaxhighlight lang="ecmascript">/* Module "vector.wren" */ |
<syntaxhighlight lang="ecmascript">/* Module "vector.wren" */ |
||
/* |
|||
/* Vector represents a two dimensional geometric vector in the plane. */ |
|||
Vector represents a vector in n-dimensional Euclidean space. |
|||
For two or three dimensional geometric vectors, the specialist |
|||
classes Vector2 or Vector3 should normally be used instead |
|||
as they have greater functionality including support for |
|||
construction using coordinate systems other than Cartesian. |
|||
*/ |
|||
class Vector { |
class Vector { |
||
// |
// Returns a zero Vector of dimension 'n'. |
||
static |
static zero(n) { Vector.new([0] * n) } |
||
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. |
// Returns v1 * v2 or v2 * v1 depending on which order the arguments are presented. |
||
Line 19: | Line 21: | ||
} |
} |
||
// Efficiently sums a list of |
// Efficiently sums a list of Vectors of the same dimension. |
||
static sumAll( |
static sumAll(Vectors) { |
||
if (!( |
if (!(Vectors is List) || Vectors.count == 0 || !(Vectors[0] is Vector)) { |
||
Fiber.abort("Argument must be a non-empty list of |
Fiber.abort("Argument must be a non-empty list of Vectors of the same dimension.") |
||
} |
|||
if (Vectors.count == 1) return Vectors[0].copy() |
|||
var n = Vectors[0].n |
|||
var si = List.filled(n, 0) |
|||
for (i in 0...n) { |
|||
si[i] = Vectors.map { |v| v[i] }.reduce { |acc, e| acc + e } |
|||
} |
|||
return Vector.new(si) |
|||
} |
|||
// Constructs a Vector from a non-empty list of cartesian coordinates. |
|||
construct new(a) { |
|||
if (!(a is List) || a.count == 0 || !(a[0] is Num)) { |
|||
Fiber.abort("Argument must be a non-empty list of numbers.") |
|||
} |
|||
_a = a.toList |
|||
_n = a.count // save dimension as a separate field |
|||
} |
|||
// Self-evident properties. |
|||
n { _n } |
|||
[i] { |
|||
if (!(i is Num) || !(i.isInteger) || i < 0 || i >= _n) { |
|||
Fiber.abort("Index must be an integer between 0 and %(_n - 1) inclusive.") |
|||
} |
|||
return _a[i] |
|||
} |
|||
[i]=(v) { |
|||
if (!(i is Num) || !(i.isInteger) || i < 0 || i >= _n) { |
|||
Fiber.abort("Index must be an integer between 0 and %(_n - 1) inclusive.") |
|||
} |
|||
if (!(v is Num)) Fiber.abort("Value must be a number.") |
|||
return _a[i] = v |
|||
} |
|||
square { a.map { |v| v * v }.reduce { |acc, e| acc + e } } |
|||
length { square.sqrt } |
|||
manhattan { a.map { |v| v.abs }.reduce { |acc, e| acc + e } } |
|||
// Returns a unit Vector with the same direction as this one. |
|||
unit { |
|||
if (length == 0) return Vector.zero(_n) |
|||
return Vector.new(a.map { |v| v / length }.toList) |
|||
} |
|||
// Basic operations. |
|||
-{ Vector.new(a.map { |v| -v }.toList) } |
|||
+(other) { |
|||
if (other is Num) { |
|||
return Vector.new((0..._n).map { |i| this[i] + other }.toList) |
|||
} |
|||
if (!(other is Vector) || _n != other.n) { |
|||
Fiber.abort("Other must be a Vector of dimension %(_n).") |
|||
} |
|||
return Vector.new((0..._n).map { |i| this[i] + other[i] }.toList) |
|||
} |
|||
-(other) { this + (-other) } |
|||
*(n) { |
|||
if (!(n is Num)) Fiber.abort("n must be a number.") |
|||
return Vector.new(a.map { |v| v * n }.toList) |
|||
} |
|||
/(n) { |
|||
if (!(n is Num)) Fiber.abort("n must be a number.") |
|||
return Vector.new(a.map { |v| v / n }.toList) |
|||
} |
|||
==(other) { |
|||
if (!(other is Vector || _n != other.n)) { |
|||
Fiber.abort("Other must be a Vector of dimension %(_n).") |
|||
} |
|||
return (0..._n).all { |i| this[i] == other[i] } |
|||
} |
|||
!=(other) { !(this == other) } |
|||
// Returns the dot product of this and another Vector of the same dimension. |
|||
dot(other) { |
|||
if (!(other is Vector) || _n != other.n) { |
|||
Fiber.abort("Other must be a Vector of dimension %(_n).") |
|||
} |
|||
return (0..._n).map { |i| this[i] * other[i] }.reduce { |acc, e| acc + e } |
|||
} |
|||
// Returns whether or not this Vector is perpendicular to another one. |
|||
isPerpTo(other) { this.dot(other) == 0 } |
|||
// Returns the distance between this and another Vector. |
|||
dist(other) { |
|||
if (!(other is Vector) || _n != other.n) { |
|||
Fiber.abort("Other must be a Vector of dimension %(_n).") |
|||
} |
|||
return (0..._n).map { |i| this[i] - other[i] }.reduce(0) { |acc, e| acc + e * e }.sqrt |
|||
} |
|||
// Returns a copy of this Vector. |
|||
copy() { Vector.new(_a) } |
|||
// Returns the cartesian coordinates of this Vector as a list. |
|||
toCartesian { _a.toList } |
|||
// Returns a string representation of this Vector in cartesian coordinates. |
|||
toString { "(" + _a.join(", ") + ")" } |
|||
} |
|||
/* Vector2 represents a two dimensional geometric vector in the plane. */ |
|||
class Vector2 { |
|||
// 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 Vector2. |
|||
static zero { Vector2.new(0, 0) } |
|||
// Returns v1 * v2 or v2 * v1 depending on which order the arguments are presented. |
|||
static scale(v1, v2) { |
|||
if ((v1 is Vector2) && (v2 is Num)) return v1 * v2 |
|||
if ((v1 is Num) && (v2 is Vector2)) return v2 * v1 |
|||
Fiber.abort("One argument must be a Vector2 and the other a number.") |
|||
} |
|||
// Efficiently sums a list of vector2s. |
|||
static sumAll(vector2s) { |
|||
if (!(vector2s is List) || vector2s.count == 0 || !(vector2s[0] is Vector2)) { |
|||
Fiber.abort("Argument must be a non-empty list of vector2s.") |
|||
} |
} |
||
if ( |
if (vector2s.count == 1) return vector2s[0].copy() |
||
var sx = vector2s.map { |v| v.x }.reduce { |acc, x| acc + x } |
|||
if (vectors.count == 2) return vectors[0] + vectors[1] |
|||
var |
var sy = vector2s.map { |v| v.y }.reduce { |acc, y| acc + y } |
||
return Vector2.new(sx, sy) |
|||
var sy = vectors.map { |v| v.y }.reduce { |acc, y| acc + y } |
|||
return Vector.new(sx, sy) |
|||
} |
} |
||
// Constructs a |
// Constructs a Vector2 from polar coordinates. |
||
static fromPolar(r, theta) { |
static fromPolar(r, theta) { |
||
if (!(r is Num) || !(theta is Num)) Fiber.abort("Arguments must both be numbers.") |
if (!(r is Num) || !(theta is Num)) Fiber.abort("Arguments must both be numbers.") |
||
Line 38: | Line 170: | ||
} |
} |
||
// Constructs a |
// Constructs a Vector2 from cartesian coordinates. |
||
construct new(x, y) { |
construct new(x, y) { |
||
if (!(x is Num) || !(y is Num)) Fiber.abort("Arguments must both be numbers.") |
if (!(x is Num) || !(y is Num)) Fiber.abort("Arguments must both be numbers.") |
||
Line 63: | Line 195: | ||
} |
} |
||
// Returns a |
// Returns a Vector2 of the same length which is perpendicular to this one. |
||
perp { |
perp { Vector2.new(-_y, x) } |
||
// Returns a unit |
// Returns a unit Vector2 with the same direction as this one. |
||
unit { |
unit { |
||
if (length == 0) return |
if (length == 0) return Vector2.new(0, 0) |
||
return |
return Vector2.new(_x/length, _y/length) |
||
} |
} |
||
// Basic operations. |
// Basic operations. |
||
-{ |
-{ Vector2.new(-_x, -_y) } |
||
-(other) { this + (-other) } |
|||
+(other) { |
+(other) { |
||
if |
if (other is Num) return Vector2.new(_x + other, _y + other) |
||
if (!(other is Vector2)) Fiber.abort("Other must be a Vector2 or a scalar.") |
|||
return Vector.new(_x + other.x, _y + other.y) |
|||
return Vector2.new(_x + other.x, _y + other.y) |
|||
} |
} |
||
-(other) { this + (-other) } |
|||
*(n) { |
*(n) { |
||
if (!(n is Num)) Fiber.abort("n must be a number.") |
if (!(n is Num)) Fiber.abort("n must be a number.") |
||
return |
return Vector2.new(_x * n, _y * n) |
||
} |
} |
||
/(n) { |
/(n) { |
||
if (!(n is Num)) Fiber.abort("n must be a number.") |
if (!(n is Num)) Fiber.abort("n must be a number.") |
||
return |
return Vector2.new(_x / n, _y / n) |
||
} |
} |
||
==(other) { |
==(other) { |
||
if (!(other is |
if (!(other is Vector2)) Fiber.abort("Other must be a Vector2.") |
||
return x == other.x && _y == other.y |
return x == other.x && _y == other.y |
||
} |
} |
||
Line 100: | Line 233: | ||
!=(other) { !(this == other) } |
!=(other) { !(this == other) } |
||
// Returns the dot product of this and another |
// Returns the dot product of this and another Vector2. |
||
dot(other) { |
dot(other) { |
||
if (!(other is |
if (!(other is Vector2)) Fiber.abort("Other must be a Vector2.") |
||
return _x * other.x + _y * other.y |
return _x * other.x + _y * other.y |
||
} |
} |
||
// Returns whether or not this |
// Returns whether or not this Vector2 is perpendicular to another one. |
||
isPerpTo(other) { |
isPerpTo(other) { this.dot(other) == 0 } |
||
if (!(other is Vector)) Fiber.abort("Other must be a Vector.") |
|||
return this.dot(other) == 0 |
|||
} |
|||
// Returns the angle between this |
// Returns the angle between this Vector2 and another one. |
||
angleBetween(other) { |
angleBetween(other) { |
||
if (!(other is Vector)) Fiber.abort("Other must be a Vector.") |
|||
var v = (this.dot(other)/(length * other.length)).acos |
var v = (this.dot(other)/(length * other.length)).acos |
||
if (__useDeg) v = v * 180 / Num.pi |
if (__useDeg) v = v * 180 / Num.pi |
||
Line 120: | Line 249: | ||
} |
} |
||
// Returns the distance between this and another |
// Returns the distance between this and another Vector2. |
||
dist(other) { |
dist(other) { |
||
if (!(other is |
if (!(other is Vector2)) Fiber.abort("Other must be a Vector2.") |
||
var d1 = _x - other.x |
var d1 = _x - other.x |
||
var d2 = _y - other.y |
var d2 = _y - other.y |
||
Line 128: | Line 257: | ||
} |
} |
||
// Returns a copy of this |
// Returns a copy of this Vector2. |
||
copy() { |
copy() { Vector2.new(_x, _y) } |
||
// Returns the cartesian coordinates of this |
// Returns the cartesian coordinates of this Vector2 as a list. |
||
toCartesian { [_x, _y] } |
toCartesian { [_x, _y] } |
||
// Returns the polar coordinates of this |
// Returns the polar coordinates of this Vector2 as a list. |
||
toPolar { !__useDeg ? [radius, theta] : [radius, theta * 180 / Num.pi] } |
toPolar { !__useDeg ? [radius, theta] : [radius, theta * 180 / Num.pi] } |
||
// |
// Converts this Vector2 to a generic Vector. |
||
toVector { Vector.new(toCartesian) } |
|||
// Returns a string representation of this Vector2 in cartesian coordinates. |
|||
toString { "(%(_x), %(_y))" } |
toString { "(%(_x), %(_y))" } |
||
} |
} |
||
Line 163: | Line 295: | ||
} |
} |
||
if (vector3s.count == 1) return vector3s[0].copy() |
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 sx = vector3s.map { |v| v.x }.reduce { |acc, x| acc + x } |
||
var sy = vector3s.map { |v| v.y }.reduce { |acc, y| acc + y } |
var sy = vector3s.map { |v| v.y }.reduce { |acc, y| acc + y } |
||
Line 239: | Line 370: | ||
+(other) { |
+(other) { |
||
if |
if (other is Num) return Vector3.new(_x + other, _y + other, _z + other) |
||
if (!(other is Vector3)) Fiber.abort("Other must be a Vector3 or a scalar.") |
|||
return Vector3.new(_x + other.x, _y + other.y, _z + other.z) |
return Vector3.new(_x + other.x, _y + other.y, _z + other.z) |
||
} |
} |
||
Line 279: | Line 411: | ||
// Returns the scalar triple product of this and two other Vector3s. |
// Returns the scalar triple product of this and two other Vector3s. |
||
scalarTripleProd(other1, other2) { |
scalarTripleProd(other1, other2) { this.dot(other1.cross(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. |
// Returns the vector triple product of this and two other Vector3s. |
||
vectorTripleProd(other1, other2) { |
vectorTripleProd(other1, other2) { this.cross(other1.cross(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. |
// Returns the scalar quadruple product of this and three other Vector3s. |
||
scalarQuodProd(other1, other2, other3) { |
scalarQuodProd(other1, other2, other3) { this.cross(other1).dot(other2.cross(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. |
// Returns the vector quadruple product of this and three other Vector3s. |
||
vectorQuodProd(other1, other2, other3) { |
vectorQuodProd(other1, other2, other3) { this.cross(other1).cross(other2.cross(other3)) } |
||
if (!(other1 is Vector3) || !(other2 is Vector3) || !(other3 is Vector3)) { |
|||
// Returns whether or not this Vector3 is perpendicular to another one. |
|||
Fiber.abort("Arguments must all be Vector3s.") |
|||
} |
isPerpTo(other) { this.dot(other) == 0 } |
||
return this.cross(other1).cross(other2.cross(other3)) |
|||
} |
|||
// Returns the angle between this Vector3 and another one. |
// Returns the angle between this Vector3 and another one. |
||
angleBetween(other) { |
angleBetween(other) { |
||
if (!(other is Vector3)) Fiber.abort("Other must be a Vector3.") |
|||
var v = (this.dot(other)/(length * other.length)).acos |
var v = (this.dot(other)/(length * other.length)).acos |
||
if (__useDeg) v = v * 180 / Num.pi |
if (__useDeg) v = v * 180 / Num.pi |
||
Line 320: | Line 434: | ||
// Returns the distance between this and another Vector3. |
// Returns the distance between this and another Vector3. |
||
dist(other) { |
dist(other) { |
||
if (!(other is |
if (!(other is Vector3)) Fiber.abort("Other must be a Vector3.") |
||
var d1 = _x - other.x |
var d1 = _x - other.x |
||
var d2 = _y - other.y |
var d2 = _y - other.y |
||
Line 334: | Line 448: | ||
// Returns the cylindrical coordinates of this Vector3 as a list. |
// Returns the cylindrical coordinates of this Vector3 as a list. |
||
toCylindrical { !__useDeg ? [radius, theta, _z] : [radius, theta * 180 / Num.pi , _z]} |
toCylindrical { !__useDeg ? [radius, theta, _z] : [radius, theta * 180 / Num.pi , _z]} |
||
// Returns the spherical coordinates of this Vector3 as a list. |
// Returns the spherical coordinates of this Vector3 as a list. |
||
toSpherical { !__useDeg ? [length, theta, phi] : [length, theta * 180 /Num.pi, phi * 180 / Num.pi] } |
toSpherical { !__useDeg ? [length, theta, phi] : [length, theta * 180 /Num.pi, phi * 180 / Num.pi] } |
||
// Converts this Vector3 to a generic Vector. |
|||
toVector { Vector.new(toCartesian) } |
|||
// Returns a string representation of this Vector3 in cartesian coordinates. |
// Returns a string representation of this Vector3 in cartesian coordinates. |
||
Line 344: | Line 461: | ||
// Set initial angle unit defaults (radians). |
// Set initial angle unit defaults (radians). |
||
Vector2.useDegrees = false |
|||
Vector3.useDegrees = false |
Vector3.useDegrees = false</syntaxhighlight> |
||
var Vector2 = Vector // alias for Vector class</syntaxhighlight> |