Category talk:Wren-ellipse

From Rosetta Code

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