Category talk:Wren-vector

From Rosetta Code
Revision as of 10:06, 20 March 2023 by PureFox (talk | contribs) (Added source code for new 'Wren-vector' module.)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Source code

/* 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