Category talk:Wren-polygon

From Rosetta Code

Source code

/* Module "polygon.wren" */

import "graphics" for Canvas, Color
import "math" for Point, Vector

/* Selectable is an abstract class representing the interface needed for an identifiable
   object to be selectable by mouse click or otherwise */
class Selectable {
    tag     { "" }
    tag=(t) {}

    selected     { false }
    selected=(s) {}

    contains(x, y) { false }
}

/* Polygon represents a polygon in 2 dimensional space. */
class Polygon is Selectable {

    /* Private static helper methods for 'overlaps' instance method. */

    static getAxes_(poly) {
        var axes = List.filled(poly.sides, null)
        var vertices = poly.vertices
        for (i in 0...poly.sides) {
            var vertex1 = vertices[i]
            var vertex2 = vertices[(i+1 == poly.sides) ? 0 : i+1]
            var vector1 = Vector.new(vertex1[0], vertex1[1])
            var vector2 = Vector.new(vertex2[0], vertex2[1])
            var edge = vector1 - vector2
            axes[i] = edge.perp
        }
        return axes
    }

    static projectOntoAxis_(poly, axis) {
        var vertices = poly.vertices
        var vertex0 = vertices[0]
        var vector0 = Vector.new(vertex0[0], vertex0[1])
        var min = axis.dot(vector0)
        var max = min
        for (i in 1...poly.sides) {
            var vertex = vertices[i]
            var vector = Vector.new(vertex[0], vertex[1])
            var p = axis.dot(vector)
            if (p < min) {
                min = p
            } else if (p > max) {
                max = p
            }
        }
        return [min, max]
    }

    static projectionsOverlap_(proj1, proj2) {
        if (proj1[1] < proj2[0]) return false
        if (proj2[1] < proj1[0]) return false
        return true
    }

    // Constructs a new Polygon object from its vertices and tag.
    construct new(vertices, tag) {
        if (vertices.count < 3) Fiber.abort("Number of vertices cannot be less than 3.")
        if (vertices[0].type != List || vertices[0].count != 2 || vertices[0][0].type != Num) {
            Fiber.abort("Each vertex must be a pair of numbers.")
        }
        _v = vertices.toList
        _tag = tag
        _selected = false
    }

    // Constructs a new Polygon object from its vertices and tag without checking or copying them.
    construct quick(vertices, tag) {
        _v = vertices
        _tag = tag
        _selected = false
    }

    // Constructs a new regular Polygon object with n sides, centered at (cx, cy), radius r from the
    // center to each vertex and start angle sa in degrees measured clockwise from the horizonal line 
    // drawn to the polygon's rightmost point.
    static regular(n, cx, cy, r, sa, tag) {
        var vertices = List.filled(n, null)
        var inc = 360 / n
        var angle = sa
        for (i in 0...n) {
            var a = angle * Num.pi / 180
            var x = a.cos * r + cx
            var y = a.sin * r + cy
            vertices[i] = [x, y]
            angle = angle + inc
        }
        return Polygon.new(vertices, tag)
    }

    // Convenience versions of the above constructors for objects with an empty tag.
    static new(vertices) { new(vertices, "") }

    static quick(vertices) { quick(vertices, "") }

    static regular(n, cx, cy, r, sa) { regular(n, cx, cy, r, sa, "") }

    // Properties
    sides        { _v.count }      // returns the number of sides of the curent instance
    vertices     { _v.toList }     // returns a shallow copy of the current instance's vertices
    tag          { _tag }          // gets the tag of the current instance
    tag=(t)      { _tag = t }      // sets the tag of the current instance 
    selected     { _selected }     // gets whether the current instance is selected or not.
    selected=(s) { _selected = s } // sets whether the current instance is selected or not.

    // Returns what kind of polygon this instance is.
    kind {
        return (sides < 11) ?
            ["triangle", "quadrilateral", "pentagon", "hexagon",
             "octagon", "nonagon", "decagon"][sides-3] : "%(sides)-sided polygon"
    }

    // Returns whether the current instance contains a point (x, y) using the 'even-odd' rule.
    // Vertices or points on the boundary are considered to be contained by the polygon.
    contains(x, y) {
        var n = sides
        var j =  n - 1
        var contained = false
        for (i in 0...n) {
            if (x == _v[i][0] && y == _v[i][1]) return true  // vertex
            if ((_v[i][1] > y) != (_v[j][1] > y)) {
                var slope = (x-_v[i][0])*(_v[j][1]-_v[i][1]) - (_v[j][0]-_v[i][0])*(y-_v[i][1])
                if (slope == 0) return true  // point on boundary
                if ((slope < 0) != (_v[j][1] < _v[i][1])) contained = !contained
            }
            j = i
        }
        return contained
    }

    // Returns whether a point (x, y) is a vertex of the current instance.
    hasVertex(x, y) {
        for (i in 0...sides) {
            if (x == _v[i][0] && y == _v[i][1]) return true
        }
        return false
    }

    // Returns whether a point (x, y) is a vertex or on the boundary of the current instance.
    hasEdgePoint(x, y) {
        var n = sides
        var j =  n - 1
        for (i in 0...n) {
            if (x == _v[i][0] && y == _v[i][1]) return true  // vertex
            if ((_v[i][1] > y) != (_v[j][1] > y)) {
                var slope = (x-_v[i][0])*(_v[j][1]-_v[i][1]) - (_v[j][0]-_v[i][0])*(y-_v[i][1])
                if (slope == 0) return true  // point on boundary
            }
            j = i
        }
        return false
    }

    // Returns whether a point (x, y) is an interior point of the current instance.
    // Vertices or points on the boundary are not considered to be interior ponts.
    hasInteriorPoint(x, y) {
        var n = sides
        var j =  n - 1
        var contained = false
        for (i in 0...n) {
            if (x == _v[i][0] && y == _v[i][1]) return false  // vertex
            if ((_v[i][1] > y) != (_v[j][1] > y)) {
                var slope = (x-_v[i][0])*(_v[j][1]-_v[i][1]) - (_v[j][0]-_v[i][0])*(y-_v[i][1])
                if (slope == 0) return false  // point on boundary
                if ((slope < 0) != (_v[j][1] < _v[i][1])) contained = !contained
            }
            j = i
        }
        return contained
    }

    // Returns whether this instance overlaps another Polygon object i.e. they have
    // at least one point in common.
    overlaps(other) {
        if (!(other is Polygon)) Fiber.abort("Argument must be a Polygon.")
        var axes1 = Polygon.getAxes_(this)
        var axes2 = Polygon.getAxes_(other)
        for (axes in [axes1, axes2]) {
            for (axis in axes) {
                var proj1 = Polygon.projectOntoAxis_(this, axis)
                var proj2 = Polygon.projectOntoAxis_(other, axis)
                if (!Polygon.projectionsOverlap_(proj1, proj2)) return false
            }
        }
        return true
    }

    // Draws the current instance in a given color and pixel size.
    draw(col, size) {
        if (col.type != Color) Fiber.abort("First argument must be a color.")
        if (size.type != Num  || !size.isInteger || size < 1) {
            Fiber.abort("Size must be a positive integer.")
        } 
        var j =  sides - 1
        for (i in 0...j) {
            Canvas.line(_v[i][0], _v[i][1], _v[i+1][0], _v[i+1][1], col, size)
        }
        Canvas.line(_v[j][0], _v[j][1], _v[0][0], _v[0][1], col, size)
    }

    // Draws the current instance in a given color and pixel size of 1.
    draw(col) { draw(col, 1) }

    // Draws the current instance filled with a given color.
    drawfill(col) {
        if (col.type != Color) Fiber.abort("First argument must be a color.")
        // get coordinates of bounding rectangle
        var vx = _v.map { |v| v[0] }
        var vy = _v.map { |v| v[1] }
        var xmin = vx.reduce { |acc, x| (x < acc) ? x : acc } 
        var xmax = vx.reduce { |acc, x| (x > acc) ? x : acc }
        var ymin = vy.reduce { |acc, y| (y < acc) ? y : acc } 
        var ymax = vy.reduce { |acc, y| (y > acc) ? y : acc }
        for (x in xmin..xmax) {
            for (y in ymin..ymax) {
                if (contains(x, y)) Canvas.pset(x, y, col)
            }
        }
    }

    // Draws the current instance using a given fill color, border color and border thickness.
    drawfill(fillColor, borderColor, borderSize) {
        drawfill(fillColor)
        draw(borderColor, borderSize)
    }

    // Draws the current instance using a given fill color, border color and border thickness of 1.
    drawfill(fillColor, borderColor) {
        drawfill(fillColor)
        draw(borderColor)
    }

    // Returns the string representation of the current instance.
    toString { _vertices.toString }
}

/* Rectangle represents a rectangle in 2 dimensional space. */
class Rectangle is Polygon {
     // Constructs a new Rectangle object with top left corner (x, y), width w and height h.
    construct new (x, y, w, h, tag) {
        if (!((x is Num) && (y is Num) && (w is Num) && (h is Num))) {
            Fiber.abort("First four arguments must be numbers.")
        }
        super([[x, y], [x + w, y], [x + w, y + h] , [x, y + h]], tag)
        _x = x
        _y = y
        _w = w
        _h = h
    }

    // Constructs a new Rectangle object with center (cx, cy), width w and height h.
    static fromCenter(cx, cy, w, h, tag) { new(cx - w/2, cy - h/2, w, h, tag) }

    // Convenience versions of the above constructors for objects with an empty tag.
    static new(x, y, w, h) { new(x, y, w, h, "") }

    static fromCenter(cx, cy, w, h) { fromCenter(cx, cy, w, h, "") }
    
    // Properties
    x      { _x  }
    y      { _y  }
    cx     { _x + _w/2 }
    cy     { _y + _h/2 }
    width  { _w }
    height { _h }

    topLeft   { Point.new(_x, _y) }
    center    { Point.new(cx, cy) }
    perimeter { 2 * (_w + _h) }
    area      { _w * _h }
}

/* Square represents a square in 2 dimensional space. */
class Square is Rectangle {
     // Constructs a new Square object with top left corner (x, y) and side s.
    construct new (x, y, s, tag) {
        super(x, y, s, s, tag)
        _s = s
    }

    // Constructs a new Square object with center (cx, cy) and side s.
    static fromCenter(cx, cy, s, tag) { new(cx - s/2, cy - s/2, s, tag) }

    // Convenience versions of the above constructors for objects with an empty tag.
    static new(x, y, s) { new(x, y, s, "") }

    static fromCenter(cx, cy, s) { fromCenter(cx, cy, s, "") }

    // Properties.
    side { _s }
}