How to resolve the algorithm Bézier curves/Intersections step by step in the Go programming language
How to resolve the algorithm Bézier curves/Intersections step by step in the Go programming language
Table of Contents
Problem Statement
You are given two planar quadratic Bézier curves, having control points
( − 1 , 0 ) , ( 0 , 10 ) , ( 1 , 0 )
{\displaystyle (-1,0),(0,10),(1,0)}
and
( 2 , 1 ) , ( − 8 , 2 ) , ( 2 , 3 )
{\displaystyle (2,1),(-8,2),(2,3)}
, respectively. They are parabolas intersecting at four points, as shown in the following diagram:
The task is to write a program that finds all four intersection points and prints their
( x , y )
{\displaystyle (x,y)}
coordinates. You may use any algorithm you know of or can think of, including any of those that others have used.
Let's start with the solution:
Step by Step solution about How to resolve the algorithm Bézier curves/Intersections step by step in the Go programming language
Overview
This code addresses a problem in computational geometry: finding the intersections between two planar quadratic Bézier curves. These curves are defined by three control points, and the overall idea is to subdivide the curves into smaller segments until the rectangles containing these segments overlap, suggesting a potential intersection. If the overlap is small enough, the midpoint of the overlapping rectangle is considered an intersection point, but with a tolerance to avoid duplicates.
Data Structures
point
: Represents a 2D point withx
andy
coordinatesquadSpline
: A parametric spline representing a quadratic curve segmentquadCurve
: A parametric planar curve composed of twoquadSpline
curves (x and y components)
Key Functions
subdivideQuadSpline
: Subdivides aquadSpline
into two newquadSpline
curves based on a parametert
.subdivideQuadCurve
: Subdivides aquadCurve
into two newquadCurve
curves based on a parametert
.rectsOverlap
: Checks if two rectangles (defined by their minimum and maximum x and y values) overlap.testIntersect
: Tests if twoquadCurve
curves intersect, updating a flag indicating if the intersection can be excluded or accepted (found), and storing the intersection point if found.seemsToBeDuplicate
: Checks if a given point is close enough to any existing intersection points (within a specified tolerance) to be considered a duplicate.findIntersects
: Divides thequadCurve
curves into smaller segments until potential intersections are found.
Main Function
The main
function:
- Defines two
quadCurve
curves (p
andq
). - Defines a tolerance threshold (
tol
) and a spacing threshold to avoid duplicate intersections (spacing
). - Invokes
findIntersects
to find the intersections between the curves. - Prints the found intersection points.
Usage
Imagine two quadratic Bézier curves, each defined by three control points. This code repeatedly subdivides both curves until their bounding rectangles overlap within a certain tolerance. If the overlap is small enough, it considers the midpoint of the overlapping rectangle an intersection point. It also checks for duplicate intersections within a specified tolerance to avoid reporting the same intersection multiple times.
Example
Consider the following two quadratic Bézier curves:
Curve p
:
- Control points: (-1, 0), (0, 10), (1, 0)
Curve q
:
- Control points: (2, -8), (2, 3)
Using a tolerance and spacing threshold of 0.0000001 and 0.000001 respectively, the program would identify and print the following intersection point:
(0.333333, 2.666667)
Source code in the go programming language
/* The control points of a planar quadratic Bézier curve form a
triangle--called the "control polygon"--that completely contains
the curve. Furthermore, the rectangle formed by the minimum and
maximum x and y values of the control polygon completely contain
the polygon, and therefore also the curve.
Thus a simple method for narrowing down where intersections might
be is: subdivide both curves until you find "small enough" regions
where these rectangles overlap.
*/
package main
import (
"fmt"
"log"
"math"
)
type point struct {
x, y float64
}
type quadSpline struct { // Non-parametric spline.
c0, c1, c2 float64
}
type quadCurve struct { // Planar parametric spline.
x, y quadSpline
}
// Subdivision by de Casteljau's algorithm.
func subdivideQuadSpline(q quadSpline, t float64, u, v *quadSpline) {
s := 1.0 - t
c0 := q.c0
c1 := q.c1
c2 := q.c2
u.c0 = c0
v.c2 = c2
u.c1 = s*c0 + t*c1
v.c1 = s*c1 + t*c2
u.c2 = s*u.c1 + t*v.c1
v.c0 = u.c2
}
func subdivideQuadCurve(q quadCurve, t float64, u, v *quadCurve) {
subdivideQuadSpline(q.x, t, &u.x, &v.x)
subdivideQuadSpline(q.y, t, &u.y, &v.y)
}
// It is assumed that xa0 <= xa1, ya0 <= ya1, xb0 <= xb1, and yb0 <= yb1.
func rectsOverlap(xa0, ya0, xa1, ya1, xb0, yb0, xb1, yb1 float64) bool {
return (xb0 <= xa1 && xa0 <= xb1 && yb0 <= ya1 && ya0 <= yb1)
}
func max3(x, y, z float64) float64 { return math.Max(math.Max(x, y), z) }
func min3(x, y, z float64) float64 { return math.Min(math.Min(x, y), z) }
// This accepts the point as an intersection if the boxes are small enough.
func testIntersect(p, q quadCurve, tol float64, exclude, accept *bool, intersect *point) {
pxmin := min3(p.x.c0, p.x.c1, p.x.c2)
pymin := min3(p.y.c0, p.y.c1, p.y.c2)
pxmax := max3(p.x.c0, p.x.c1, p.x.c2)
pymax := max3(p.y.c0, p.y.c1, p.y.c2)
qxmin := min3(q.x.c0, q.x.c1, q.x.c2)
qymin := min3(q.y.c0, q.y.c1, q.y.c2)
qxmax := max3(q.x.c0, q.x.c1, q.x.c2)
qymax := max3(q.y.c0, q.y.c1, q.y.c2)
*exclude = true
*accept = false
if rectsOverlap(pxmin, pymin, pxmax, pymax, qxmin, qymin, qxmax, qymax) {
*exclude = false
xmin := math.Max(pxmin, qxmin)
xmax := math.Min(pxmax, pxmax)
if xmax < xmin {
log.Fatalf("Assertion failure: %f < %f\n", xmax, xmin)
}
if xmax-xmin <= tol {
ymin := math.Max(pymin, qymin)
ymax := math.Min(pymax, qymax)
if ymax < ymin {
log.Fatalf("Assertion failure: %f < %f\n", ymax, ymin)
}
if ymax-ymin <= tol {
*accept = true
intersect.x = 0.5*xmin + 0.5*xmax
intersect.y = 0.5*ymin + 0.5*ymax
}
}
}
}
func seemsToBeDuplicate(intersects []point, xy point, spacing float64) bool {
seemsToBeDup := false
i := 0
for !seemsToBeDup && i != len(intersects) {
pt := intersects[i]
seemsToBeDup = math.Abs(pt.x-xy.x) < spacing && math.Abs(pt.y-xy.y) < spacing
i++
}
return seemsToBeDup
}
func findIntersects(p, q quadCurve, tol, spacing float64) []point {
var intersects []point
type workset struct {
p, q quadCurve
}
workload := []workset{workset{p, q}}
// Quit looking after having emptied the workload.
for len(workload) > 0 {
l := len(workload)
work := workload[l-1]
workload = workload[0 : l-1]
var exclude, accept bool
intersect := point{0, 0}
testIntersect(work.p, work.q, tol, &exclude, &accept, &intersect)
if accept {
// To avoid detecting the same intersection twice, require some
// space between intersections.
if !seemsToBeDuplicate(intersects, intersect, spacing) {
intersects = append(intersects, intersect)
}
} else if !exclude {
var p0, p1, q0, q1 quadCurve
subdivideQuadCurve(work.p, 0.5, &p0, &p1)
subdivideQuadCurve(work.q, 0.5, &q0, &q1)
workload = append(workload, workset{p0, q0})
workload = append(workload, workset{p0, q1})
workload = append(workload, workset{p1, q0})
workload = append(workload, workset{p1, q1})
}
}
return intersects
}
func main() {
var p, q quadCurve
p.x = quadSpline{-1.0, 0.0, 1.0}
p.y = quadSpline{0.0, 10.0, 0.0}
q.x = quadSpline{2.0, -8.0, 2.0}
q.y = quadSpline{1.0, 2.0, 3.0}
tol := 0.0000001
spacing := tol * 10
intersects := findIntersects(p, q, tol, spacing)
for _, intersect := range intersects {
fmt.Printf("(% f, %f)\n", intersect.x, intersect.y)
}
}
You may also check:How to resolve the algorithm Determine if two triangles overlap step by step in the Pascal programming language
You may also check:How to resolve the algorithm Speech synthesis step by step in the BASIC256 programming language
You may also check:How to resolve the algorithm Sequence of non-squares step by step in the Eiffel programming language
You may also check:How to resolve the algorithm Call an object method step by step in the Ursa programming language
You may also check:How to resolve the algorithm Haversine formula step by step in the Raku programming language