Category talk:Wren-vector

Source code

/* Module "vector.wren" */

/*
    Vector represents a vector in n-dimensional Euclidean space.
    For two or three dimensional geometric vectors, the specialized
    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 {
    // Returns a zero Vector of dimension 'n'.
    static zero(n) { Vector.new([0] * n) }

    // 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 of the same dimension.
    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 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 (vector2s.count == 1) return vector2s[0].copy()
        var sx = vector2s.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)
    }

    // Constructs a Vector2 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 Vector2 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 Vector2 of the same length which is perpendicular to this one.
    perp { Vector2.new(-_y, x) }

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

    // Basic operations.

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

    +(other) {
        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 Vector2.new(_x + other.x, _y + other.y)
    }

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

    *(n) {
        if (!(n is Num)) Fiber.abort("n must be a number.")
        return Vector2.new(_x * n, _y * n)
    }

    /(n) {
        if (!(n is Num)) Fiber.abort("n must be a number.")
        return Vector2.new(_x / n, _y / n)
    }

    ==(other) {
        if (!(other is Vector2)) Fiber.abort("Other must be a Vector2.")
        return x == other.x && _y == other.y
    }

    !=(other) { !(this == other) }

    // Returns the dot product of this and another Vector2.
    dot(other) {
        if (!(other is Vector2)) Fiber.abort("Other must be a Vector2.")
        return _x * other.x + _y * other.y
    }

    // Returns whether or not this Vector2 is perpendicular to another one.
    isPerpTo(other) { this.dot(other) == 0 }

    // Returns the angle between this Vector2 and another one.
    angleBetween(other) {
        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 Vector2.
    dist(other) {
        if (!(other is Vector2)) Fiber.abort("Other must be a Vector2.")
        var d1 = _x - other.x
        var d2 = _y - other.y
        return (d1 * d1 + d2 * d2).sqrt
    }

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

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

/* 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()
        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 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)
    }

    -(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 * other.z
    }

    // 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) { this.dot(other1.cross(other2)) }

    // Returns the vector triple product of this and two other Vector3s.
    vectorTripleProd(other1, other2) { this.cross(other1.cross(other2)) }

    // Returns the scalar quadruple product of this and three other Vector3s.
    scalarQuodProd(other1, other2, other3) { this.cross(other1).dot(other2.cross(other3)) }

    // Returns the vector quadruple product of this and three other Vector3s.
    vectorQuodProd(other1, other2, other3) { this.cross(other1).cross(other2.cross(other3)) }

    // Returns whether or not this Vector3 is perpendicular to another one.
    isPerpTo(other) { this.dot(other) == 0 }

    // Returns the angle between this Vector3 and another one.
    angleBetween(other) {
        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 Vector3)) 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] }

    // Converts this Vector3 to a generic Vector.
    toVector { Vector.new(toCartesian) }

    // Returns a string representation of this Vector3 in cartesian coordinates.
    toString { "(%(_x), %(_y), %(_z))" }
}

// Set initial angle unit defaults (radians).
Vector2.useDegrees = false
Vector3.useDegrees = false
Return to "Wren-vector" page.