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 {
// Gets or sets the default angle unit.
// Returns a zero Vector of dimension 'n'.
static useDegrees { __useDeg }
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 vectors.
// Efficiently sums a list of Vectors of the same dimension.
static sumAll(vectors) {
static sumAll(Vectors) {
if (!(vectors is List) || vectors.count == 0 || !(vectors[0] is Vector)) {
if (!(Vectors is List) || Vectors.count == 0 || !(Vectors[0] is Vector)) {
Fiber.abort("Argument must be a non-empty list of vectors.")
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 (vectors.count == 1) return vectors[0].copy()
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 sx = vectors.map { |v| v.x }.reduce { |acc, x| acc + x }
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 Vector from polar coordinates.
// 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 Vector from cartesian coordinates.
// 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 Vector of the same length which is perpendicular to this one.
// Returns a Vector2 of the same length which is perpendicular to this one.
perp { Vector.new(-_y, x) }
perp { Vector2.new(-_y, x) }


// Returns a unit Vector with the same direction as this one.
// Returns a unit Vector2 with the same direction as this one.
unit {
unit {
if (length == 0) return Vector.new(0, 0)
if (length == 0) return Vector2.new(0, 0)
return Vector.new(_x/length, _y/length)
return Vector2.new(_x/length, _y/length)
}
}


// Basic operations.
// Basic operations.


-{ Vector.new(-_x, -_y) }
-{ Vector2.new(-_x, -_y) }

-(other) { this + (-other) }


+(other) {
+(other) {
if (!(other is Vector)) Fiber.abort("Other must be a Vector.")
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 Vector.new(_x * n, _y * n)
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 Vector.new(_x / n, _y / n)
return Vector2.new(_x / n, _y / n)
}
}


==(other) {
==(other) {
if (!(other is Vector)) Fiber.abort("Other must be a Vector.")
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 Vector.
// Returns the dot product of this and another Vector2.
dot(other) {
dot(other) {
if (!(other is Vector)) Fiber.abort("Other must be a Vector.")
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 Vector is perpendicular to another one.
// 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 Vector and another one.
// 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 Vector.
// Returns the distance between this and another Vector2.
dist(other) {
dist(other) {
if (!(other is Vector)) Fiber.abort("Other must be a Vector.")
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 Vector.
// Returns a copy of this Vector2.
copy() { Vector.new(_x, _y) }
copy() { Vector2.new(_x, _y) }

// Returns the cartesian coordinates of this Vector as a list.
// Returns the cartesian coordinates of this Vector2 as a list.
toCartesian { [_x, _y] }
toCartesian { [_x, _y] }


// Returns the polar coordinates of this Vector as a list.
// 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] }


// Returns a string representation of this Vector in cartesian coordinates.
// 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 (!(other is Vector3)) Fiber.abort("Other must be a Vector3.")
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 Vector)) Fiber.abort("Other must be a Vector3.")
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).
Vector.useDegrees = false
Vector2.useDegrees = false
Vector3.useDegrees = false
Vector3.useDegrees = false</syntaxhighlight>

var Vector2 = Vector // alias for Vector class</syntaxhighlight>