Add quadratic subdivision
This commit is contained in:
parent
c20e243ba4
commit
4dd2a24c2d
6 changed files with 217 additions and 73 deletions
|
@ -2,6 +2,8 @@ include $(GOROOT)/src/Make.inc
|
|||
|
||||
TARG=draw2d.googlecode.com/hg/draw2d/curve
|
||||
GOFILES=\
|
||||
curve_float64.go\
|
||||
cubic_float64.go\
|
||||
quad_float64.go\
|
||||
cubic_float64_others.go\
|
||||
|
||||
include $(GOROOT)/src/Make.pkg
|
||||
|
|
41
draw2d/curve/_testmain.go
Normal file
41
draw2d/curve/_testmain.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package main
|
||||
|
||||
import "draw2d.googlecode.com/hg/draw2d/curve"
|
||||
import "testing"
|
||||
import __os__ "os"
|
||||
import __regexp__ "regexp"
|
||||
|
||||
var tests = []testing.InternalTest{
|
||||
{"curve.TestCubicCurveRec", curve.TestCubicCurveRec},
|
||||
{"curve.TestCubicCurve", curve.TestCubicCurve},
|
||||
{"curve.TestCubicCurveAdaptiveRec", curve.TestCubicCurveAdaptiveRec},
|
||||
{"curve.TestCubicCurveAdaptive", curve.TestCubicCurveAdaptive},
|
||||
{"curve.TestCubicCurveParabolic", curve.TestCubicCurveParabolic},
|
||||
{"curve.TestQuadCurve", curve.TestQuadCurve},
|
||||
}
|
||||
|
||||
var benchmarks = []testing.InternalBenchmark{{"curve.BenchmarkCubicCurveRec", curve.BenchmarkCubicCurveRec},
|
||||
{"curve.BenchmarkCubicCurve", curve.BenchmarkCubicCurve},
|
||||
{"curve.BenchmarkCubicCurveAdaptiveRec", curve.BenchmarkCubicCurveAdaptiveRec},
|
||||
{"curve.BenchmarkCubicCurveAdaptive", curve.BenchmarkCubicCurveAdaptive},
|
||||
{"curve.BenchmarkCubicCurveParabolic", curve.BenchmarkCubicCurveParabolic},
|
||||
{"curve.BenchmarkQuadCurve", curve.BenchmarkQuadCurve},
|
||||
}
|
||||
|
||||
var matchPat string
|
||||
var matchRe *__regexp__.Regexp
|
||||
|
||||
func matchString(pat, str string) (result bool, err __os__.Error) {
|
||||
if matchRe == nil || matchPat != pat {
|
||||
matchPat = pat
|
||||
matchRe, err = __regexp__.Compile(matchPat)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return matchRe.MatchString(str), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
testing.Main(matchString, tests, benchmarks)
|
||||
}
|
69
draw2d/curve/cubic_float64.go
Normal file
69
draw2d/curve/cubic_float64.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 17/05/2011 by Laurent Le Goff
|
||||
package curve
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
const (
|
||||
CurveRecursionLimit = 32
|
||||
)
|
||||
|
||||
type CubicCurveFloat64 struct {
|
||||
X1, Y1, X2, Y2, X3, Y3, X4, Y4 float64
|
||||
}
|
||||
|
||||
type LineTracer interface {
|
||||
LineTo(x, y float64)
|
||||
}
|
||||
|
||||
func (c *CubicCurveFloat64) Subdivide(c1, c2 *CubicCurveFloat64) (x23, y23 float64) {
|
||||
// Calculate all the mid-points of the line segments
|
||||
//----------------------
|
||||
c1.X1, c1.Y1 = c.X1, c.Y1
|
||||
c2.X4, c2.Y4 = c.X4, c.Y4
|
||||
c1.X2 = (c.X1 + c.X2) / 2
|
||||
c1.Y2 = (c.Y1 + c.Y2) / 2
|
||||
x23 = (c.X2 + c.X3) / 2
|
||||
y23 = (c.Y2 + c.Y3) / 2
|
||||
c2.X3 = (c.X3 + c.X4) / 2
|
||||
c2.Y3 = (c.Y3 + c.Y4) / 2
|
||||
c1.X3 = (c1.X2 + x23) / 2
|
||||
c1.Y3 = (c1.Y2 + y23) / 2
|
||||
c2.X2 = (x23 + c2.X3) / 2
|
||||
c2.Y2 = (y23 + c2.Y3) / 2
|
||||
c1.X4 = (c1.X3 + c2.X2) / 2
|
||||
c1.Y4 = (c1.Y3 + c2.Y2) / 2
|
||||
c2.X1, c2.Y1 = c1.X4, c1.Y4
|
||||
return
|
||||
}
|
||||
|
||||
func (curve *CubicCurveFloat64) Segment(t LineTracer, flattening_threshold float64) {
|
||||
// Add the first point
|
||||
t.LineTo(curve.X1, curve.Y1)
|
||||
|
||||
var curves [CurveRecursionLimit]CubicCurveFloat64
|
||||
curves[0] = *curve
|
||||
i := 0
|
||||
// current curve
|
||||
var c *CubicCurveFloat64
|
||||
var dx, dy, d2, d3 float64
|
||||
for i >= 0 {
|
||||
c = &curves[i]
|
||||
dx = c.X4 - c.X1
|
||||
dy = c.Y4 - c.Y1
|
||||
|
||||
d2 = math.Fabs(((c.X2-c.X4)*dy - (c.Y2-c.Y4)*dx))
|
||||
d3 = math.Fabs(((c.X3-c.X4)*dy - (c.Y3-c.Y4)*dx))
|
||||
|
||||
if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
|
||||
t.LineTo(c.X4, c.Y4)
|
||||
i--
|
||||
} else {
|
||||
// second half of bezier go lower onto the stack
|
||||
c.Subdivide(&curves[i+1], &curves[i])
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,19 +7,10 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
CurveRecursionLimit = 32
|
||||
CurveCollinearityEpsilon = 1e-30
|
||||
CurveAngleToleranceEpsilon = 0.01
|
||||
)
|
||||
|
||||
type CubicCurveFloat64 struct {
|
||||
X1, Y1, X2, Y2, X3, Y3, X4, Y4 float64
|
||||
}
|
||||
|
||||
type LineTracer interface {
|
||||
LineTo(x, y float64)
|
||||
}
|
||||
|
||||
|
||||
//mu ranges from 0 to 1, start to end of curve
|
||||
func (c *CubicCurveFloat64) ArbitraryPoint(mu float64) (x, y float64) {
|
||||
|
@ -60,27 +51,6 @@ func (c *CubicCurveFloat64) SubdivideAt(c1, c2 *CubicCurveFloat64, t float64) (x
|
|||
return
|
||||
}
|
||||
|
||||
func (c *CubicCurveFloat64) Subdivide(c1, c2 *CubicCurveFloat64) (x23, y23 float64) {
|
||||
// Calculate all the mid-points of the line segments
|
||||
//----------------------
|
||||
c1.X1, c1.Y1 = c.X1, c.Y1
|
||||
c2.X4, c2.Y4 = c.X4, c.Y4
|
||||
c1.X2 = (c.X1 + c.X2) / 2
|
||||
c1.Y2 = (c.Y1 + c.Y2) / 2
|
||||
x23 = (c.X2 + c.X3) / 2
|
||||
y23 = (c.Y2 + c.Y3) / 2
|
||||
c2.X3 = (c.X3 + c.X4) / 2
|
||||
c2.Y3 = (c.Y3 + c.Y4) / 2
|
||||
c1.X3 = (c1.X2 + x23) / 2
|
||||
c1.Y3 = (c1.Y2 + y23) / 2
|
||||
c2.X2 = (x23 + c2.X3) / 2
|
||||
c2.Y2 = (y23 + c2.Y3) / 2
|
||||
c1.X4 = (c1.X3 + c2.X2) / 2
|
||||
c1.Y4 = (c1.Y3 + c2.Y2) / 2
|
||||
c2.X1, c2.Y1 = c1.X4, c1.Y4
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CubicCurveFloat64) EstimateDistance() float64 {
|
||||
dx1 := c.X2 - c.X1
|
||||
dy1 := c.Y2 - c.Y1
|
||||
|
@ -121,35 +91,6 @@ func (c *CubicCurveFloat64) segmentRec(t LineTracer, flattening_threshold float6
|
|||
c2.segmentRec(t, flattening_threshold)
|
||||
}
|
||||
|
||||
func (curve *CubicCurveFloat64) Segment(t LineTracer, flattening_threshold float64) {
|
||||
// Add the first point
|
||||
t.LineTo(curve.X1, curve.Y1)
|
||||
|
||||
var curves [CurveRecursionLimit]CubicCurveFloat64
|
||||
curves[0] = *curve
|
||||
i := 0
|
||||
// current curve
|
||||
var c *CubicCurveFloat64
|
||||
var dx, dy, d2, d3 float64
|
||||
for i >= 0 {
|
||||
c = &curves[i]
|
||||
dx = c.X4 - c.X1
|
||||
dy = c.Y4 - c.Y1
|
||||
|
||||
d2 = math.Fabs(((c.X2-c.X4)*dy - (c.Y2-c.Y4)*dx))
|
||||
d3 = math.Fabs(((c.X3-c.X4)*dy - (c.Y3-c.Y4)*dx))
|
||||
|
||||
if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
|
||||
t.LineTo(c.X4, c.Y4)
|
||||
i--
|
||||
} else {
|
||||
// second half of bezier go lower onto the stack
|
||||
c.Subdivide(&curves[i+1], &curves[i])
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The function has the following parameters:
|
||||
approximationScale :
|
|
@ -15,7 +15,7 @@ import (
|
|||
|
||||
var (
|
||||
flattening_threshold float64 = 0.25
|
||||
testsFloat64 = []CubicCurveFloat64{
|
||||
testsCubicFloat64 = []CubicCurveFloat64{
|
||||
CubicCurveFloat64{100, 100, 200, 100, 100, 200, 200, 200},
|
||||
CubicCurveFloat64{100, 100, 300, 200, 200, 200, 300, 100},
|
||||
CubicCurveFloat64{100, 100, 0, 300, 200, 0, 300, 300},
|
||||
|
@ -23,6 +23,14 @@ var (
|
|||
CubicCurveFloat64{10, 290, 10, 10, 290, 10, 290, 290},
|
||||
CubicCurveFloat64{100, 290, 290, 10, 10, 10, 200, 290},
|
||||
}
|
||||
testsQuadFloat64 = []QuadCurveFloat64{
|
||||
QuadCurveFloat64{100, 100, 200, 100, 200, 200},
|
||||
QuadCurveFloat64{100, 100, 290, 200, 290, 100},
|
||||
QuadCurveFloat64{100, 100, 0, 290, 200, 290},
|
||||
QuadCurveFloat64{150, 290, 10, 10, 290, 290},
|
||||
QuadCurveFloat64{10, 290, 10, 10, 290, 290},
|
||||
QuadCurveFloat64{100, 290, 290, 10, 120, 290},
|
||||
}
|
||||
)
|
||||
|
||||
type Path struct {
|
||||
|
@ -50,9 +58,12 @@ func init() {
|
|||
defer f.Close()
|
||||
log.Printf("Create html viewer")
|
||||
f.Write([]byte("<html><body>"))
|
||||
for i := 0; i < len(testsFloat64); i++ {
|
||||
for i := 0; i < len(testsCubicFloat64); i++ {
|
||||
f.Write([]byte(fmt.Sprintf("<div><img src='_testRec%d.png'/>\n<img src='_test%d.png'/>\n<img src='_testAdaptiveRec%d.png'/>\n<img src='_testAdaptive%d.png'/>\n<img src='_testParabolic%d.png'/>\n</div>\n", i, i, i, i, i)))
|
||||
}
|
||||
for i := 0; i < len(testsQuadFloat64); i++ {
|
||||
f.Write([]byte(fmt.Sprintf("<div><img src='_testQuad%d.png'/>\n</div>\n", i)))
|
||||
}
|
||||
f.Write([]byte("</body></html>"))
|
||||
|
||||
}
|
||||
|
@ -95,7 +106,7 @@ func drawPoints(img draw.Image, c image.Color, s ...float64) image.Image {
|
|||
}
|
||||
|
||||
func TestCubicCurveRec(t *testing.T) {
|
||||
for i, curve := range testsFloat64 {
|
||||
for i, curve := range testsCubicFloat64 {
|
||||
var p Path
|
||||
curve.SegmentRec(&p, flattening_threshold)
|
||||
img := image.NewNRGBA(300, 300)
|
||||
|
@ -110,7 +121,7 @@ func TestCubicCurveRec(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCubicCurve(t *testing.T) {
|
||||
for i, curve := range testsFloat64 {
|
||||
for i, curve := range testsCubicFloat64 {
|
||||
var p Path
|
||||
curve.Segment(&p, flattening_threshold)
|
||||
img := image.NewNRGBA(300, 300)
|
||||
|
@ -125,7 +136,7 @@ func TestCubicCurve(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCubicCurveAdaptiveRec(t *testing.T) {
|
||||
for i, curve := range testsFloat64 {
|
||||
for i, curve := range testsCubicFloat64 {
|
||||
var p Path
|
||||
curve.AdaptiveSegmentRec(&p, 1, 0, 0)
|
||||
img := image.NewNRGBA(300, 300)
|
||||
|
@ -140,7 +151,7 @@ func TestCubicCurveAdaptiveRec(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCubicCurveAdaptive(t *testing.T) {
|
||||
for i, curve := range testsFloat64 {
|
||||
for i, curve := range testsCubicFloat64 {
|
||||
var p Path
|
||||
curve.AdaptiveSegment(&p, 1, 0, 0)
|
||||
img := image.NewNRGBA(300, 300)
|
||||
|
@ -155,7 +166,7 @@ func TestCubicCurveAdaptive(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCubicCurveParabolic(t *testing.T) {
|
||||
for i, curve := range testsFloat64 {
|
||||
for i, curve := range testsCubicFloat64 {
|
||||
var p Path
|
||||
curve.ParabolicSegment(&p, flattening_threshold)
|
||||
img := image.NewNRGBA(300, 300)
|
||||
|
@ -169,9 +180,24 @@ func TestCubicCurveParabolic(t *testing.T) {
|
|||
fmt.Println()
|
||||
}
|
||||
|
||||
|
||||
func TestQuadCurve(t *testing.T) {
|
||||
for i, curve := range testsQuadFloat64 {
|
||||
var p Path
|
||||
curve.Segment(&p, flattening_threshold)
|
||||
img := image.NewNRGBA(300, 300)
|
||||
raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3)
|
||||
raster.PolylineBresenham(img, image.Black, p.points...)
|
||||
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
|
||||
savepng(fmt.Sprintf("_testQuad%d.png", i), img)
|
||||
log.Printf("Num of points: %d\n", len(p.points))
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func BenchmarkCubicCurveRec(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, curve := range testsFloat64 {
|
||||
for _, curve := range testsCubicFloat64 {
|
||||
p := Path{make([]float64, 0, 32)}
|
||||
curve.SegmentRec(&p, flattening_threshold)
|
||||
}
|
||||
|
@ -180,7 +206,7 @@ func BenchmarkCubicCurveRec(b *testing.B) {
|
|||
|
||||
func BenchmarkCubicCurve(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, curve := range testsFloat64 {
|
||||
for _, curve := range testsCubicFloat64 {
|
||||
p := Path{make([]float64, 0, 32)}
|
||||
curve.Segment(&p, flattening_threshold)
|
||||
}
|
||||
|
@ -189,7 +215,7 @@ func BenchmarkCubicCurve(b *testing.B) {
|
|||
|
||||
func BenchmarkCubicCurveAdaptiveRec(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, curve := range testsFloat64 {
|
||||
for _, curve := range testsCubicFloat64 {
|
||||
p := Path{make([]float64, 0, 32)}
|
||||
curve.AdaptiveSegmentRec(&p, 1, 0, 0)
|
||||
}
|
||||
|
@ -198,7 +224,7 @@ func BenchmarkCubicCurveAdaptiveRec(b *testing.B) {
|
|||
|
||||
func BenchmarkCubicCurveAdaptive(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, curve := range testsFloat64 {
|
||||
for _, curve := range testsCubicFloat64 {
|
||||
p := Path{make([]float64, 0, 32)}
|
||||
curve.AdaptiveSegment(&p, 1, 0, 0)
|
||||
}
|
||||
|
@ -207,9 +233,18 @@ func BenchmarkCubicCurveAdaptive(b *testing.B) {
|
|||
|
||||
func BenchmarkCubicCurveParabolic(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, curve := range testsFloat64 {
|
||||
for _, curve := range testsCubicFloat64 {
|
||||
p := Path{make([]float64, 0, 32)}
|
||||
curve.ParabolicSegment(&p, flattening_threshold)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkQuadCurve(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, curve := range testsQuadFloat64 {
|
||||
p := Path{make([]float64, 0, 32)}
|
||||
curve.Segment(&p, flattening_threshold)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
56
draw2d/curve/quad_float64.go
Normal file
56
draw2d/curve/quad_float64.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 17/05/2011 by Laurent Le Goff
|
||||
package curve
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
type QuadCurveFloat64 struct {
|
||||
X1, Y1, X2, Y2, X3, Y3 float64
|
||||
}
|
||||
|
||||
|
||||
func (c *QuadCurveFloat64) Subdivide(c1, c2 *QuadCurveFloat64) {
|
||||
// Calculate all the mid-points of the line segments
|
||||
//----------------------
|
||||
c1.X1, c1.Y1 = c.X1, c.Y1
|
||||
c2.X3, c2.Y3 = c.X3, c.Y3
|
||||
c1.X2 = (c.X1 + c.X2) / 2
|
||||
c1.Y2 = (c.Y1 + c.Y2) / 2
|
||||
c2.X2 = (c.X2 + c.X3) / 2
|
||||
c2.Y2 = (c.Y2 + c.Y3) / 2
|
||||
c1.X3 = (c1.X2 + c2.X2) / 2
|
||||
c1.Y3 = (c1.Y2 + c2.Y2) / 2
|
||||
c2.X1, c2.Y1 = c1.X3, c1.Y3
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
func (curve *QuadCurveFloat64) Segment(t LineTracer, flattening_threshold float64) {
|
||||
// Add the first point
|
||||
t.LineTo(curve.X1, curve.Y1)
|
||||
|
||||
var curves [CurveRecursionLimit]QuadCurveFloat64
|
||||
curves[0] = *curve
|
||||
i := 0
|
||||
// current curve
|
||||
var c *QuadCurveFloat64
|
||||
var dx, dy, d float64
|
||||
for i >= 0 {
|
||||
c = &curves[i]
|
||||
dx = c.X3 - c.X1
|
||||
dy = c.Y3 - c.Y1
|
||||
|
||||
d = math.Fabs(((c.X2-c.X3)*dy - (c.Y2-c.Y3)*dx))
|
||||
|
||||
if (d*d) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
|
||||
t.LineTo(c.X3, c.Y3)
|
||||
i--
|
||||
} else {
|
||||
// second half of bezier go lower onto the stack
|
||||
c.Subdivide(&curves[i+1], &curves[i])
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue