Category talk:Wren-polygon

Revision as of 16:47, 23 August 2021 by PureFox (talk | contribs) (→‎Source code: Infrastructure changes to enable all polygons to be treated as buttons and to make it easier to track button clicks.)

Source code

<lang ecmascript>/* Module "polygon.wren" */

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

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

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

}</lang>

Return to "Wren-polygon" page.