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/server negotiation, protocol
|
||||||
* Client can request chunks from server for local rendering.
|
* Client can request chunks from server for local rendering.
|
||||||
* Players send inputs over network sockets.
|
* 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
|
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 (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/pkg/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// IterLine is a generator that returns the X,Y coordinates to draw a line.
|
// 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
|
return generator
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterEllipse is a generator that draws out the pixels of an ellipse.
|
// IterEllipse iterates an Ellipse using two Points as the top-left and
|
||||||
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
|
|
||||||
// bottom-right corners of a rectangle that encompasses the ellipse.
|
// 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 (
|
var (
|
||||||
// xc = float32(A.X+B.X) / 2
|
width = AbsInt32(B.X - A.X)
|
||||||
// yc = float32(A.Y+B.Y) / 2
|
height = AbsInt32(B.Y - A.Y)
|
||||||
xc = float32(B.X)
|
radius = NewPoint(width/2, height/2)
|
||||||
yc = float32(B.Y)
|
center = NewPoint(AbsInt32(B.X-radius.X), AbsInt32(B.Y-radius.Y))
|
||||||
rx = float32(B.X - A.X)
|
|
||||||
ry = float32(B.Y - A.Y)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if rx < 0 {
|
return MidpointEllipse(center, radius)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,7 @@ func (s *Stroke) IterPoints() chan render.Point {
|
||||||
ch <- point
|
ch <- point
|
||||||
}
|
}
|
||||||
case Ellipse:
|
case Ellipse:
|
||||||
for point := range render.IterEllipse2(s.PointA, s.PointB) {
|
for point := range render.IterEllipse(s.PointA, s.PointB) {
|
||||||
ch <- point
|
ch <- point
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user