diff --git a/examples/overlapping_circles.go b/examples/overlapping_circles.go new file mode 100644 index 0000000..38208b2 --- /dev/null +++ b/examples/overlapping_circles.go @@ -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) + } +} diff --git a/examples/overlapping_circles.py b/examples/overlapping_circles.py new file mode 100644 index 0000000..05cf593 --- /dev/null +++ b/examples/overlapping_circles.py @@ -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()