overlapping circles
This commit is contained in:
parent
fbf7f5bdca
commit
bb32105061
|
@ -0,0 +1,191 @@
|
|||
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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import axi
|
||||
import os
|
||||
|
||||
N_PER_ROW = 8
|
||||
SPACING = 2
|
||||
|
||||
def load(path):
|
||||
d = axi.Drawing.load(path)
|
||||
d = d.scale_to_fit(1.9, 1.9)
|
||||
d = d.join_paths(0.5 / 25.4)
|
||||
d = d.sort_paths()
|
||||
d = d.join_paths(0.5 / 25.4)
|
||||
d = d.origin()
|
||||
return d
|
||||
|
||||
def main():
|
||||
dirname = 'overlapping_circles'
|
||||
i = 0
|
||||
j = 0
|
||||
x = 0
|
||||
y = 0
|
||||
drawing = axi.Drawing([])
|
||||
for filename in sorted(os.listdir(dirname)):
|
||||
if not filename.endswith('.axi'):
|
||||
continue
|
||||
path = os.path.join(dirname, filename)
|
||||
print(path)
|
||||
d = load(path)
|
||||
d = d.translate(x, y)
|
||||
drawing.add(d)
|
||||
x += SPACING
|
||||
i += 1
|
||||
if i == N_PER_ROW:
|
||||
i = 0
|
||||
j += 1
|
||||
x = 0
|
||||
if j % 2:
|
||||
x = SPACING / 2
|
||||
y += SPACING * 0.866
|
||||
d = drawing
|
||||
d = d.center(*axi.A3_SIZE)
|
||||
print(len(d.paths))
|
||||
im = d.render(bounds=axi.A3_BOUNDS, line_width = 0.4 / 25.4)
|
||||
im.write_to_png('overlapping_circles.png')
|
||||
d.dump('overlapping_circles.axi')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue