axi/examples/overlapping_circles.go

192 lines
3.6 KiB
Go
Raw Permalink Normal View History

2019-11-23 16:19:18 +00:00
package main
import (
"fmt"
"math"
"os"
"sort"
)
const (
// D = math.Pi
S = 2
)
type Point struct {
X, Y float64
}
func (a Point) Lerp(b Point, t float64) Point {
x := a.X + (b.X-a.X)*t
y := a.Y + (b.Y-a.Y)*t
return Point{x, y}
}
type Segment struct {
P0, P1 Point
}
type Circle struct {
X, Y, R float64
}
func (c Circle) ContainsPoint(p Point) bool {
return math.Hypot(p.X-c.X, p.Y-c.Y) < c.R
}
func (c Circle) Discretize(n int) []Point {
points := make([]Point, n)
for i := range points {
t := float64(i) / float64(n-1)
a := 2 * math.Pi * t
x := math.Cos(a)*c.R + c.X
y := math.Sin(a)*c.R + c.Y
points[i] = Point{x, y}
}
return points
}
func (c Circle) IntersectLine(p0, p1 Point) (float64, float64, bool) {
dx := p1.X - p0.X
dy := p1.Y - p0.Y
A := dx*dx + dy*dy
B := 2 * (dx*(p0.X-c.X) + dy*(p0.Y-c.Y))
C := (p0.X-c.X)*(p0.X-c.X) + (p0.Y-c.Y)*(p0.Y-c.Y) - c.R*c.R
det := B*B - 4*A*C
if A <= 0 || det <= 0 {
return 0, 0, false
}
t0 := (-B + math.Sqrt(det)) / (2 * A)
t1 := (-B - math.Sqrt(det)) / (2 * A)
return t0, t1, true
}
func makeCircles(circleRadius, visibleRadius float64) []Circle {
var circles []Circle
a := int(math.Ceil(circleRadius + visibleRadius))
for y := -a; y <= a; y++ {
for x := -a; x <= a; x++ {
cx := float64(x)
cy := float64(y)
if math.Hypot(cx, cy) <= circleRadius+visibleRadius {
circles = append(circles, Circle{cx, cy, circleRadius})
}
}
}
return circles
}
func count(circles []Circle, p Point) int {
var result int
for _, c := range circles {
if c.ContainsPoint(p) {
result++
}
}
return result
}
type splitFunc func(Point) bool
func split(circles []Circle, p0, p1 Point, f splitFunc) []Segment {
var ts []float64
for _, c := range circles {
t0, t1, ok := c.IntersectLine(p0, p1)
if ok {
ts = append(ts, t0)
ts = append(ts, t1)
}
}
sort.Float64s(ts)
var segments []Segment
for i := 1; i < len(ts); i++ {
t0 := ts[i-1]
t1 := ts[i]
if t1 < 0 || t0 > 1 {
continue
}
t0 = math.Max(t0, 0)
t1 = math.Min(t1, 1)
t := (t0 + t1) / 2
p := p0.Lerp(p1, t)
if f(p) {
q0 := p0.Lerp(p1, t0)
q1 := p0.Lerp(p1, t1)
segments = append(segments, Segment{q0, q1})
}
}
return segments
}
func run(path string, d, s float64) error {
file, err := os.Create(path)
if err != nil {
return err
}
circles := makeCircles(d/2, s*math.Sqrt(2))
x0 := -s
x1 := s
y0 := -s
y1 := s
f := func(p Point) bool {
return count(circles, p)%2 == 1
}
g := func(p Point) bool {
return math.Hypot(p.X, p.Y) <= s
}
outer := []Circle{Circle{0, 0, s}}
const n = 200
for i := 0; i <= n; i++ {
t := float64(i) / float64(n)
y := y0 + (y1-y0)*t
p0 := Point{x0, y}
p1 := Point{x1, y}
segments := split(circles, p0, p1, f)
for _, s := range segments {
clipped := split(outer, s.P0, s.P1, g)
for _, cs := range clipped {
fmt.Fprintf(file, "%g,%g %g,%g\n", cs.P0.X, cs.P0.Y, cs.P1.X, cs.P1.Y)
}
}
}
for _, c := range circles {
points := c.Discretize(360)
for i := 1; i < len(points); i++ {
p0 := points[i-1]
p1 := points[i]
clipped := split(outer, p0, p1, g)
for _, cs := range clipped {
fmt.Fprintf(file, "%g,%g %g,%g\n", cs.P0.X, cs.P0.Y, cs.P1.X, cs.P1.Y)
}
}
}
points := outer[0].Discretize(360)
for _, p := range points {
fmt.Fprintf(file, "%g,%g ", p.X, p.Y)
}
fmt.Fprintf(file, "\n")
return nil
}
func main() {
d0 := 1.0
d1 := math.Pi
n := 48
for i := 0; i < n; i++ {
t := float64(i) / float64(n-1)
d := d0 + (d1-d0)*t
path := fmt.Sprintf("overlapping_circles/%.8f.axi", d)
fmt.Println(path)
run(path, d, S)
}
}