Better Ellipse Drawing Algorithm
This commit is contained in:
parent
0c6c77a423
commit
17b18b8c0a
16
TODO.md
16
TODO.md
|
@ -86,3 +86,19 @@ In addition to those listed above:
|
|||
* Client/server negotiation, protocol
|
||||
* Client can request chunks from server for local rendering.
|
||||
* Players send inputs over network sockets.
|
||||
|
||||
## Far Off Ideas
|
||||
|
||||
### Level Styles (Top-down vs Side Scrolling)
|
||||
|
||||
It might be cool to support multiple "styles" of level, apart from the current
|
||||
2D platforming-with-gravity style.
|
||||
|
||||
For example a top-down perspective level would let the player freely walk in
|
||||
both axes and would probably have a full set of unique Doodads drawn in that
|
||||
perspective.
|
||||
|
||||
Migration path: a GameStyle enum would be added to Levels and Doodads as an
|
||||
integer type, default 0 is the current 2D platformer style, 1 for top-down,
|
||||
etc. -- so existing levels and doodads would default to 0 on upgrade and new
|
||||
levels/doodads would use the new value.
|
||||
|
|
64
lib/render/ellipse.go
Normal file
64
lib/render/ellipse.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package render
|
||||
|
||||
// MidpointEllipse implements an ellipse plotting algorithm.
|
||||
func MidpointEllipse(center, radius Point) chan Point {
|
||||
yield := make(chan Point)
|
||||
go func() {
|
||||
|
||||
var (
|
||||
pos = NewPoint(radius.X, 0)
|
||||
delta = NewPoint(
|
||||
2*radius.Y*radius.Y*pos.X,
|
||||
2*radius.X*radius.X*pos.Y,
|
||||
)
|
||||
err = radius.X*radius.X -
|
||||
radius.Y*radius.Y*radius.X +
|
||||
(radius.Y*radius.Y)/4
|
||||
)
|
||||
|
||||
for delta.Y < delta.X {
|
||||
yield <- NewPoint(center.X+pos.X, center.Y+pos.Y)
|
||||
yield <- NewPoint(center.X+pos.X, center.Y-pos.Y)
|
||||
yield <- NewPoint(center.X-pos.X, center.Y+pos.Y)
|
||||
yield <- NewPoint(center.X-pos.X, center.Y-pos.Y)
|
||||
|
||||
pos.Y++
|
||||
|
||||
if err < 0 {
|
||||
delta.Y += 2 * radius.X * radius.X
|
||||
err += delta.Y + radius.X*radius.X
|
||||
} else {
|
||||
pos.X--
|
||||
delta.Y += 2 * radius.X * radius.X
|
||||
delta.X -= 2 * radius.Y * radius.Y
|
||||
err += delta.Y - delta.X + radius.X*radius.X
|
||||
}
|
||||
}
|
||||
|
||||
err = radius.X*radius.X*(pos.Y*pos.Y+pos.Y) +
|
||||
radius.Y*radius.Y*(pos.X-1)*(pos.X-1) -
|
||||
radius.Y*radius.Y*radius.X*radius.X
|
||||
|
||||
for pos.X >= 0 {
|
||||
yield <- NewPoint(center.X+pos.X, center.Y+pos.Y)
|
||||
yield <- NewPoint(center.X+pos.X, center.Y-pos.Y)
|
||||
yield <- NewPoint(center.X-pos.X, center.Y+pos.Y)
|
||||
yield <- NewPoint(center.X-pos.X, center.Y-pos.Y)
|
||||
|
||||
pos.X--
|
||||
|
||||
if err > 0 {
|
||||
delta.X -= 2 * radius.Y * radius.Y
|
||||
err += radius.Y*radius.Y - delta.X
|
||||
} else {
|
||||
pos.Y++
|
||||
delta.Y += 2 * radius.X * radius.X
|
||||
delta.X -= 2 * radius.Y * radius.Y
|
||||
err += delta.Y - delta.X + radius.Y*radius.Y
|
||||
}
|
||||
}
|
||||
|
||||
close(yield)
|
||||
}()
|
||||
return yield
|
||||
}
|
|
@ -98,3 +98,11 @@ func TrimBox(src, dst *Rect, p Point, S Rect, thickness int32) {
|
|||
dst.W = S.W - thickness
|
||||
}
|
||||
}
|
||||
|
||||
// AbsInt32 returns the absolute value of an int32.
|
||||
func AbsInt32(v int32) int32 {
|
||||
if v < 0 {
|
||||
return -v
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
|
|
@ -2,8 +2,6 @@ package render
|
|||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
)
|
||||
|
||||
// IterLine is a generator that returns the X,Y coordinates to draw a line.
|
||||
|
@ -93,106 +91,15 @@ func IterRect(p1, p2 Point) chan Point {
|
|||
return generator
|
||||
}
|
||||
|
||||
// IterEllipse is a generator that draws out the pixels of an ellipse.
|
||||
func IterEllipse(rx, ry, xc, yc float32) chan Point {
|
||||
generator := make(chan Point)
|
||||
|
||||
mkPoint := func(x, y float32) Point {
|
||||
return NewPoint(int32(x), int32(y))
|
||||
}
|
||||
|
||||
go func() {
|
||||
var (
|
||||
dx float32
|
||||
dy float32
|
||||
d1 float32
|
||||
d2 float32
|
||||
x float32
|
||||
y = ry
|
||||
)
|
||||
|
||||
d1 = (ry * ry) - (rx * rx * ry) + (0.25 * rx * rx)
|
||||
dx = 2 * ry * ry * x
|
||||
dy = 2 * rx * rx * y
|
||||
|
||||
// For region 1
|
||||
for dx < dy {
|
||||
// Yields points based on 4-way symmetry.
|
||||
for _, point := range []Point{
|
||||
mkPoint(x+xc, y+yc),
|
||||
mkPoint(-x+xc, y+yc),
|
||||
mkPoint(x+xc, -y+yc),
|
||||
mkPoint(-x+xc, -y+yc),
|
||||
} {
|
||||
generator <- point
|
||||
}
|
||||
|
||||
if d1 < 0 {
|
||||
x++
|
||||
dx = dx + (2 * ry * ry)
|
||||
d1 = d1 + dx + (ry * ry)
|
||||
} else {
|
||||
x++
|
||||
y--
|
||||
dx = dx + (2 * ry * ry)
|
||||
dy = dy - (2 * rx * rx)
|
||||
d1 = d1 + dx - dy + (ry * ry)
|
||||
}
|
||||
}
|
||||
|
||||
d2 = ((ry * ry) + ((x + 0.5) * (x + 0.5))) +
|
||||
((rx * rx) * ((y - 1) * (y - 1))) -
|
||||
(rx * rx * ry * ry)
|
||||
|
||||
// Region 2
|
||||
for y >= 0 {
|
||||
// Yields points based on 4-way symmetry.
|
||||
for _, point := range []Point{
|
||||
mkPoint(x+xc, y+yc),
|
||||
mkPoint(-x+xc, y+yc),
|
||||
mkPoint(x+xc, -y+yc),
|
||||
mkPoint(-x+xc, -y+yc),
|
||||
} {
|
||||
generator <- point
|
||||
}
|
||||
|
||||
if d2 > 0 {
|
||||
y--
|
||||
dy = dy - (2 * rx * rx)
|
||||
d2 = d2 + (rx * rx) - dy
|
||||
} else {
|
||||
y--
|
||||
x++
|
||||
dx = dx + (2 * ry * ry)
|
||||
dy = dy - (2 * rx * rx)
|
||||
d2 = d2 + dx - dy + (rx * rx)
|
||||
}
|
||||
}
|
||||
|
||||
close(generator)
|
||||
}()
|
||||
|
||||
return generator
|
||||
}
|
||||
|
||||
// IterEllipse2 iterates an Ellipse using two Points as the top-left and
|
||||
// IterEllipse iterates an Ellipse using two Points as the top-left and
|
||||
// bottom-right corners of a rectangle that encompasses the ellipse.
|
||||
func IterEllipse2(A, B Point) chan Point {
|
||||
func IterEllipse(A, B Point) chan Point {
|
||||
var (
|
||||
// xc = float32(A.X+B.X) / 2
|
||||
// yc = float32(A.Y+B.Y) / 2
|
||||
xc = float32(B.X)
|
||||
yc = float32(B.Y)
|
||||
rx = float32(B.X - A.X)
|
||||
ry = float32(B.Y - A.Y)
|
||||
width = AbsInt32(B.X - A.X)
|
||||
height = AbsInt32(B.Y - A.Y)
|
||||
radius = NewPoint(width/2, height/2)
|
||||
center = NewPoint(AbsInt32(B.X-radius.X), AbsInt32(B.Y-radius.Y))
|
||||
)
|
||||
|
||||
if rx < 0 {
|
||||
rx = -rx
|
||||
}
|
||||
if ry < 0 {
|
||||
ry = -ry
|
||||
}
|
||||
log.Info("Ellipse btwn=%s-%s radius=%f,%f at center %f,%f", A, B, rx, ry, xc, yc)
|
||||
return IterEllipse(rx, ry, xc, yc)
|
||||
return MidpointEllipse(center, radius)
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ func (s *Stroke) IterPoints() chan render.Point {
|
|||
ch <- point
|
||||
}
|
||||
case Ellipse:
|
||||
for point := range render.IterEllipse2(s.PointA, s.PointB) {
|
||||
for point := range render.IterEllipse(s.PointA, s.PointB) {
|
||||
ch <- point
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user