192 lines
3.6 KiB
Go
192 lines
3.6 KiB
Go
|
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)
|
||
|
}
|
||
|
}
|