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