Category talk:Wren-ellipse
Appearance
Source code
/* Module "ellipse.wren" */
import "graphics" for Canvas, Color
import "math" for Math, Point
import "./polygon" for Selectable, Polygon, Rectangle, Square
/* Ellipse represents an ellipse in 2 dimensional space. */
class Ellipse is Selectable {
// Constructs a new Ellipse object with center (cx, cy),
// horizontal radius rx and vertical radius ry.
construct new(cx, cy, rx, ry, tag) {
if (!((cx is Num) && (cy is Num) && (rx is Num) && (ry is Num))) {
Fiber.abort("All arguments must be numbers.")
}
_cx = cx
_cy = cy
_rx = rx
_ry = ry
_tag = tag
}
// Convenience version of constructor for objects with an empty tag.
static new(cx, cy, rx, ry) { new(cx, cy, rx, ry, "") }
// Properties
cx { _cx }
cy { _cy }
rx { _rx }
ry { _ry }
tag { _tag }
tag=(t) { _tag = t }
selected { _selected }
selected=(s) { _selected = s }
center { Point.new(_cx, _cy) }
horizDiameter { 2 * _rx }
vertiDiameter { 2 * _ry }
circumference { Num.pi * (3*(_rx + _ry) - ((3*_rx + _ry)*(_rx + 3*_ry)).sqrt) } // approx
area { Num.pi * _rx * _ry }
// Private helper method to determine status of a point (x, y) relative to current instance.
det_(x, y) { (x - _cx) * (x - _cx) / (_rx * _rx) + (y - _cy) * (y - _cy) / (_ry * _ry) }
// Returns whether the current instance contains a point (x, y).
// Points on the boundary are considered to be contained by the ellipse.
contains(x, y) { det_(x, y) <= 1 }
// Returns whether a point (x, y) is on the boundary of the current instance.
hasEdgePoint(x, y) { det_(x,y) == 1 }
// Returns whether a point (x, y) is an interior point of the current instance.
// Points on the boundary are not considered to be interior ponts.
hasInteriorPoint(x, y) { det_(x, y) < 1 }
// Draws the current instance in a given color.
draw(c) { Canvas.ellipse(_cx - _rx, _cy - _ry, _cx + _rx, _cy + _ry, c) }
// Draws the current instance using a given fill color and border color.
drawfill(fillColor, borderColor) {
Canvas.ellipsefill(_cx - _rx, _cy - _ry, _cx + _rx, _cy + _ry, fillColor)
draw(borderColor)
}
// Convenience version of drawfill which uses the same fill and border colors.
drawfill(c) { Canvas.ellipsefill(_cx - _rx, _cy - _ry, _cx + _rx, _cy + _ry, c) }
// Draws an arc of the current instance in a given color from startAngle to endAngle in degrees.
// Angles are measured clockwise from the radius drawn to the current instance's rightmost point.
drawArc(c, startAngle, endAngle) {
var t = startAngle
if (endAngle < startAngle) {
while (endAngle < startAngle) endAngle = endAngle + 360
}
var step = (endAngle - startAngle) / 1080
if (step == 0) return
while (t <= endAngle) {
var a = t * Num.pi / 180
var x = Math.cos(a) * _rx + _cx
var y = Math.sin(a) * _ry + _cy
Canvas.pset(x, y, c)
t = t + step
}
}
// Draws a segment of the current instance in a given color from
// startAngle to endAngle in degrees. Angles are measured as in drawArc.
drawSegment(c, startAngle, endAngle) {
drawArc(c, startAngle, endAngle)
var sa = startAngle * Num.pi / 180
var sx = Math.cos(sa) * _rx + _cx
var sy = Math.sin(sa) * _ry + _cy
if (endAngle < startAngle) {
while (endAngle < startAngle) endAngle = endAngle + 360
}
var ea = endAngle * Num.pi / 180
var ex = Math.cos(ea) * _rx + _cx
var ey = Math.sin(ea) * _ry + _cy
Canvas.line(sx, sy, ex, ey, c)
}
// Draws a segment of the current instance in a given color from
// startAngle to endAngle in degrees. Angles are measured as in drawArc.
drawSegmentfill(c, startAngle, endAngle) {
drawArc(c, startAngle, endAngle)
var t = startAngle
if (endAngle < startAngle) {
while (endAngle < startAngle) endAngle = endAngle + 360
}
var step = (endAngle - startAngle) / 1080
if (step == 0) return
var u = endAngle
while (t <= u) {
var ta = t * Num.pi / 180
var sx = Math.cos(ta) * _rx + _cx
var sy = Math.sin(ta) * _ry + _cy
var ua = u * Num.pi / 180
var ex = Math.cos(ua) * _rx + _cx
var ey = Math.sin(ua) * _ry + _cy
Canvas.line(sx, sy, ex, ey, c, 2)
t = t + step
u = u - step
}
}
// Draws a segment of the current instance in a given fill color and border color from
// startAngle to endAngle in degrees. Angles are measured as in drawArc.
drawSegmentfill(fillColor, startAngle, endAngle, borderColor) {
drawSegmentfill(fillColor, startAngle, endAngle)
drawSegment(borderColor, startAngle, endAngle)
}
// Draws a sector of the current instance in a given color from
// startAngle to endAngle in degrees. Angles are measured as in drawArc.
drawSector(c, startAngle, endAngle) {
drawArc(c, startAngle, endAngle)
var sa = startAngle * Num.pi / 180
var sx = Math.cos(sa) * _rx + _cx
var sy = Math.sin(sa) * _ry + _cy
Canvas.line(_cx, _cy, sx, sy, c)
if (endAngle < startAngle) {
while (endAngle < startAngle) endAngle = endAngle + 360
}
var ea = endAngle * Num.pi / 180
var ex = Math.cos(ea) * _rx + _cx
var ey = Math.sin(ea) * _ry + _cy
Canvas.line(_cx, _cy, ex, ey, c)
}
// Draws a filled sector of the current instance in a given color from
// startAngle to endAngle in degrees. Angles are measured as in drawArc.
drawSectorfill(c, startAngle, endAngle) {
drawSegmentfill(c, startAngle, endAngle)
var sa = startAngle * Num.pi / 180
var sx = Math.cos(sa) * _rx + _cx
var sy = Math.sin(sa) * _ry + _cy
if (endAngle < startAngle) {
while (endAngle < startAngle) endAngle = endAngle + 360
}
var ea = endAngle * Num.pi / 180
var ex = Math.cos(ea) * _rx + _cx
var ey = Math.sin(ea) * _ry + _cy
var tri = Polygon.quick([[_cx, _cy], [sx, sy], [ex, ey]])
tri.drawfill(c)
}
// Draws a sector of the current instance in a fill color and border color from
// startAngle to endAngle in degrees. Angles are measured as in drawArc.
drawSectorfill(fillColor, startAngle, endAngle, borderColor) {
drawSectorfill(fillColor, startAngle, endAngle)
drawSector(borderColor, startAngle, endAngle)
}
// Returns whether a sector of the current instance contains a point (x, y).
// Points on the boundary are considered to be contained by the sector.
sectorContains(startAngle, endAngle, x, y) {
if (!contains(x, y)) return false
var rp = ((x - _cx) * (x - _cx) + (y - _cy) * (y - _cy)).sqrt
var theta = ((x - _cx) / rp).acos * 180 / Num.pi
return startAngle <= theta && endAngle >= theta
}
// Draws the current instance as a pie using a list of colors and
// associated start angles in degrees.
drawPie(colors, startAngles) {
var slices = startAngles.count
for (i in 0...slices-1) {
var delta = startAngles[i + 1] - startAngles[i]
if (delta < 0) delta = delta + 360
if (delta <= 180) {
drawSectorfill(colors[i], startAngles[i], startAngles[i] + delta)
} else {
drawSectorfill(colors[i], startAngles[i], startAngles[i] + 180)
drawSectorfill(colors[i], startAngles[i] + 180, startAngles[i] + delta)
}
}
var delta = startAngles[0] - startAngles[-1]
if (delta < 0) delta = delta + 360
if (delta <= 180) {
drawSectorfill(colors[-1], startAngles[-1], startAngles[-1] + delta)
} else {
drawSectorfill(colors[-1], startAngles[-1], startAngles[-1] + 180)
}
}
}
/* Circle represents a circle in 2 dimensional space. */
class Circle is Ellipse {
// Constructs a new Circle object with center (x, y) and radius r.
construct new(cx, cy, r, tag) {
super(cx, cy, r, r, tag)
_r = r
}
// Convenience version of constructor for objects with an empty tag.
static new(cx, cy, r) { new(cx, cy, r, "") }
// Properties
r { _r }
diameter { _r * 2 }
circumference { 2 * Num.pi * _r }
// Draws the current instance in a given color and thickness.
draw(c, size) {
var t = _r - size
if (t < 0) t = 0
for (s in _r...t) Canvas.circle(cx, cy, s, c)
}
// Convenience version of draw which uses a default thickness of 1 pixel.
draw(c) { draw(c, 1) }
// Draws the current instance using a given fill color, border color and border thickness.
drawfill(fillColor, borderColor, borderSize) {
Canvas.circlefill(cx, cy, _r, fillColor)
draw(borderColor, borderSize)
}
// Draws the current instance using a given fill color, border color and border thickness of 1.
drawfill(fillColor, borderColor) {
Canvas.circlefill(cx, cy, _r, fillColor)
draw(borderColor)
}
// Convenience version of drawfill which uses the same fill and border colors.
drawfill(c) { Canvas.circlefill(cx, cy, _r, c) }
}
/* Button represents a rectangle with curved corners in 2 dimensional space. */
class Button is Selectable {
// Constructs a new elliptical Button object with center (x, y), width w and height h.
construct new(cx, cy, w, h, tag) {
if (!((cx is Num) && (cy is Num) && (w is Num) && (h is Num))) {
Fiber.abort("First four arguments must be numbers.")
}
_cx = cx
_cy = cy
_w = w
_h = h
_tag = tag
}
// Convenience method which constructs a square button object with center (x, y) and side s.
static square(cx, cy, s, tag) { new(cx, cy, s, s, tag) }
// Convenience versions of the above constructors for objects with an empty tag.
static new(cx, cy, w, h) { new(cx, cy, w, h, "") }
static square(cx, cy, s) { new(cx, cy, s, s, "") }
// Properties
cx { _cx }
cy { _cy }
width { _w }
height { _h }
tag { _tag }
tag=(t) { _tag = t }
selected { _selected }
selected=(s) { _selected = s }
// Draws the current instance in a given color.
draw(c) {
var rx = _w / 3
var ry = _h / 3
var cx = _cx - rx/2
var cy = _cy - ry/2
var angles = [[0, 90], [90, 180], [180, 270], [270, 360]]
var centIncs = [[ rx, ry], [0, ry], [0, 0], [rx, 0]]
var lineIncs = [[-rx, 0], [0, -ry], [rx, 0], [0, ry]]
for (i in 0..3) {
var e = Ellipse.new(cx + centIncs[i][0], cy + centIncs[i][1], rx, ry)
var startAngle = angles[i][0]
var endAngle = angles[i][1]
e.drawArc(c, startAngle, endAngle)
var a = endAngle * Num.pi / 180
var sx = Math.cos(a) * rx + e.cx
var sy = Math.sin(a) * ry + e.cy
Canvas.line(sx, sy, sx + lineIncs[i][0], sy + lineIncs[i][1], c)
}
}
// Draws the current instance filled with a given color and with a given border color.
drawfill(fillColor, borderColor) {
var rx = _w / 3
var ry = _h / 3
var cx = _cx - rx/2
var cy = _cy - ry/2
var angles = [[0, 90], [90, 180], [180, 270], [270, 360]]
var centIncs = [[ rx, ry], [0, ry], [0, 0], [rx, 0]]
var lineIncs = [[-rx, 0], [0, -ry], [rx, 0], [0, ry]]
var rectIncs = [[-rx, -ry], [0, -ry], [0, 0], [-rx, 0]]
for (i in 0..3) {
var e = Ellipse.new(cx + centIncs[i][0], cy + centIncs[i][1], rx, ry)
var startAngle = angles[i][0]
var endAngle = angles[i][1]
e.drawSectorfill(fillColor, startAngle, endAngle)
e.drawArc(borderColor, startAngle, endAngle)
var a = endAngle * Num.pi / 180
var sx = Math.cos(a) * rx + e.cx
var sy = Math.sin(a) * ry + e.cy
Canvas.line(sx, sy, sx + lineIncs[i][0], sy + lineIncs[i][1], borderColor, 2)
Rectangle.new(sx + rectIncs[i][0], sy + rectIncs[i][1], rx, ry).drawfill(fillColor)
}
// Canvas.rectfill(cx, cy, rx, ry, fillColor)
Rectangle.new(cx, cy, rx, ry).drawfill(fillColor)
}
// Convenience version of drawfill method where the fill and border colors are the same.
drawfill(c) { drawfill(c, c) }
// Returns whether the current instance contains a point (x, y).
// Points on the boundary are considered to be contained by the button.
contains(x, y) {
var rx = _w / 3
var ry = _h / 3
var cx = _cx - rx/2
var cy = _cy - ry/2
var angles = [[0, 90], [90, 180], [180, 270], [270, 360]]
var angles2 = [[0, 90], [90, 180], [90, 180], [0, 90]]
var centIncs = [[ rx, ry], [0, ry], [0, 0], [rx, 0]]
var lineIncs = [[-rx, 0], [0, -ry], [rx, 0], [0, ry]]
var rectIncs = [[-rx, -ry], [0, -ry], [0, 0], [-rx, 0]]
for (i in 0..3) {
var e = Ellipse.new(cx + centIncs[i][0], cy + centIncs[i][1], rx, ry)
var startAngle = angles2[i][0]
var endAngle = angles2[i][1]
if (e.sectorContains(startAngle, endAngle, x, y)) return true
startAngle = angles[i][0]
endAngle = angles[i][1]
var a = endAngle * Num.pi / 180
var sx = Math.cos(a) * rx + e.cx
var sy = Math.sin(a) * ry + e.cy
var rect = Rectangle.new(sx + rectIncs[i][0], sy + rectIncs[i][1], rx, ry)
if (rect.contains(x, y)) return true
}
if (Rectangle.new(cx, cy, rx, ry).contains(x, y)) return true
return false
}
}