1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-26 09:44:32 +02:00

curve/curve intersection

This commit is contained in:
Pomax
2020-08-29 12:49:23 -07:00
parent bdc7c4228d
commit f4eafa288a
10 changed files with 235 additions and 163 deletions

View File

@@ -2,20 +2,24 @@
Using de Casteljau's algorithm to split the curve we can now implement curve/curve intersection finding using a "divide and conquer" technique: Using de Casteljau's algorithm to split the curve we can now implement curve/curve intersection finding using a "divide and conquer" technique:
- Take two curves *C<sub>1</sub>* and *C<sub>2</sub>*, and treat them as a pair. 1. Take two curves *C<sub>1</sub>* and *C<sub>2</sub>*, and treat them as a pair.
- If their bounding boxes overlap, split up each curve into two sub-curves 2. If their bounding boxes overlap, split up each curve into two sub-curves
- With *C<sub>1.1</sub>*, *C<sub>1.2</sub>*, *C<sub>2.1</sub>* and *C<sub>2.2</sub>*, form four new pairs (*C<sub>1.1</sub>*,*C<sub>2.1</sub>*), (*C<sub>1.1</sub>*, *C<sub>2.2</sub>*), (*C<sub>1.2</sub>*,*C<sub>2.1</sub>*), and (*C<sub>1.2</sub>*,*C<sub>2.2</sub>*). 3. With *C<sub>1.1</sub>*, *C<sub>1.2</sub>*, *C<sub>2.1</sub>* and *C<sub>2.2</sub>*, form four new pairs (*C<sub>1.1</sub>*,*C<sub>2.1</sub>*), (*C<sub>1.1</sub>*, *C<sub>2.2</sub>*), (*C<sub>1.2</sub>*,*C<sub>2.1</sub>*), and (*C<sub>1.2</sub>*,*C<sub>2.2</sub>*).
- For each pair, check whether their bounding boxes overlap. 4. For each pair, check whether their bounding boxes overlap.
- If their bounding boxes do not overlap, discard the pair, as there is no intersection between this pair of curves. 1. If their bounding boxes do not overlap, discard the pair, as there is no intersection between this pair of curves.
- If there <em>is</em> overlap, rerun all steps for this pair. 2. If there <em>is</em> overlap, rerun all steps for this pair.
- Once the sub-curves we form are so small that they effectively occupy sub-pixel areas, we consider an intersection found, noting that we might have a cluster of multiple intersections at the sub-pixel level, out of which we pick one to act as "found" `t` value (we can either throw all but one away, we can average the cluster's `t` values, or you can do something even more creative). 5. Once the sub-curves we form are so small that they effectively occupy sub-pixel areas, we consider an intersection found, noting that we might have a cluster of multiple intersections at the sub-pixel level, out of which we pick one to act as "found" `t` value (we can either throw all but one away, we can average the cluster's `t` values, or you can do something even more creative).
This algorithm will start with a single pair, "balloon" until it runs in parallel for a large number of potential sub-pairs, and then taper back down as it homes in on intersection coordinates, ending up with as many pairs as there are intersections. This algorithm will start with a single pair, "balloon" until it runs in parallel for a large number of potential sub-pairs, and then taper back down as it homes in on intersection coordinates, ending up with as many pairs as there are intersections.
The following graphic applies this algorithm to a pair of cubic curves, one step at a time, so you can see the algorithm in action. Click the button to run a single step in the algorithm, after setting up your curves in some creative arrangement. The algorithm resets once it's found a solution, so you can try this with lots of different curves (can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!) The following graphic applies this algorithm to a pair of cubic curves, one step at a time, so you can see the algorithm in action. Click the button to run a single step in the algorithm, after setting up your curves in some creative arrangement. You can also change the value that is used in step 5 to determine whether the curves are small enough. Manipulating the curves or changing the threshold will reset the algorithm, so you can try this with lots of different curves.
<Graphic title="Curve/curve intersections" setup={this.setup} draw={this.draw}> (can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!)
<button onClick={this.stepUp}>advance one step</button>
</Graphic>
Self-intersection is dealt with in the same way, except we turn a curve into two or more curves first based on the inflection points. We then form all possible curve pairs with the resultant segments, and run exactly the same algorithm. All non-overlapping curve pairs will be removed after the first iteration, and the remaining steps home in on the curve's self-intersection points. <graphics-element title="Curve/curve intersections" width="825" src="./curve-curve.js">
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
<button class="next">Advance one step</button>
<button class="reset">Reset</button>
</graphics-element>
Finding self-intersections is effectively the same procedure, except that we're starting with a single curve, so we need to turn that into two separate curves first. This is trivially achieved by splitting at an inflection point, or if there are none, just splitting at `t=0.5` first, and then running the exact same algorithm as above, with all non-overlapping curve pairs getting removed at each iteration, and each successive step homing in on the curve's self-intersection points.

View File

@@ -0,0 +1,134 @@
let curves, curve1, curve2, next;
setup() {
setPanelCount(3);
this.pairReset();
this.setupEventListening();
setSlider(`.slide-control`, `epsilon`, 1.0);
}
pairReset() {
curve1 = new Bezier(this, 50,35, 45,235, 220,235, 220,135);
curve2 = new Bezier(this, 20,150, 120,20, 220,95, 140,240);
curves = [curve1, curve2];
resetMovable(curve1.points, curve2.points);
this.reset();
}
reset() {
if (next && next.disabled) next.disabled = false;
this.pairs = [[curve1, curve2]];
this.finals = [];
this.step = 0;
}
setupEventListening() {
next = find(`.next`);
if (next) next.listen([`click`,`touchstart`], evt => {
this.step++;
redraw();
});
let reset = find(`.reset`);
if (reset) reset.listen([`click`,`touchstart`], evt => {
this.pairReset();
redraw();
});
}
draw() {
resetTransform();
clear();
// panel 1: base curves
curves.forEach(c => {
c.drawSkeleton();
c.drawCurve();
c.drawPoints();
});
// panel 2: the current iteration step
nextPanel();
setStroke(`black`);
line(0,0,0,this.height);
this.drawIteration();
setFill(`black`);
let information = `Initial curves, threshold = ${this.epsilon}px`
if (this.step) {
information = `Curve collection at iteration ${this.step}`;
}
text(information, this.panelWidth/2, 15, CENTER);
if (this.finals.length) {
text(`${this.finals.length} intersections found.`, this.panelWidth/2, this.height - 10, CENTER);
}
// panel 3: intersections
nextPanel();
setStroke(`black`);
line(0,0,0,this.height);
this.drawIntersections();
}
drawIteration() {
if (this.step > 0) {
const pairs = this.pairs;
this.pairs = [];
pairs.forEach(pair => {
if(pair[0].length() < this.epsilon && pair[1].length() < this.epsilon)
return this.finals.push(pair);
// split two curves into four curves
const s1 = pair[0].split(0.5);
const s2 = pair[1].split(0.5);
// cross check
if (s1.left.overlaps(s2.left)) { this.pairs.push([ s1.left, s2.left ]); }
if (s1.left.overlaps(s2.right)) { this.pairs.push([ s1.left, s2.right ]); }
if (s1.right.overlaps(s2.left)) { this.pairs.push([ s1.right, s2.left ]); }
if (s1.right.overlaps(s2.right)) { this.pairs.push([ s1.right, s2.right ]); }
});
}
if (!this.pairs.length && next) {
next.disabled = true;
}
this.pairs.forEach(pair => {
pair.forEach(b => {
let curve = new Bezier(this, b.points);
curve.drawCurve();
curve.drawBoundingBox( randomColor() );
})
});
setStroke(`red`);
this.finals.forEach(pair => {
let p = pair[0].get(0.5);
circle(p.x, p.y, 3);
})
}
drawIntersections() {
curve1.drawCurve(`lightblue`);
curve2.drawCurve(`lightblue`);
noFill();
setStroke(`blue`);
let intersections = curve1.intersects(curve2);
intersections = intersections.map(v => v.split(`/`).map(t => parseFloat(t)));
intersections.forEach(p => {
p = curve1.get(p[0]);
circle(p.x, p.y, 3);
});
}
onMouseMove() {
if (this.currentPoint && this.step !== 0) {
this.reset();
redraw();
}
}
onEpsilon(value) {
this.reset();
}

View File

@@ -1,117 +0,0 @@
var abs = Math.abs;
module.exports = {
setup: function(api) {
this.api = api;
api.setPanelCount(3);
var curve1 = new api.Bezier(10,100,90,30,40,140,220,220);
var curve2 = new api.Bezier(5,150,180,20,80,250,210,190);
api.setCurve(curve1, curve2);
this.pairReset();
},
pairReset: function() {
this.prevstep = 0;
this.step = 0;
},
draw: function(api, curves) {
api.reset();
var offset = {x:0, y:0};
curves.forEach(curve => {
api.drawSkeleton(curve);
api.drawCurve(curve);
});
// next panel: iterations
var w = api.getPanelWidth();
var h = api.getPanelHeight();
offset.x += w;
api.drawLine({x:0,y:0}, {x:0,y:h}, offset);
if (this.step === 0) {
this.pairs = [{c1: curves[0], c2: curves[1]}];
}
if(this.step !== this.prevstep) {
var pairs = this.pairs;
this.pairs = [];
this.finals = [];
pairs.forEach(p => {
if(p.c1.length() < 0.6 && p.c2.length() < 0.6) {
return this.finals.push(p);
}
var s1 = p.c1.split(0.5);
api.setColor("black");
api.drawCurve(p.c1, offset);
api.setColor("red");
api.drawbbox(s1.left.bbox(), offset);
api.drawbbox(s1.right.bbox(), offset);
var s2 = p.c2.split(0.5);
api.setColor("black");
api.drawCurve(p.c2, offset);
api.setColor("blue");
api.drawbbox(s2.left.bbox(), offset);
api.drawbbox(s2.right.bbox(), offset);
if (s1.left.overlaps(s2.left)) { this.pairs.push({c1: s1.left, c2: s2.left}); }
if (s1.left.overlaps(s2.right)) { this.pairs.push({c1: s1.left, c2: s2.right}); }
if (s1.right.overlaps(s2.left)) { this.pairs.push({c1: s1.right, c2: s2.left}); }
if (s1.right.overlaps(s2.right)) { this.pairs.push({c1: s1.right, c2: s2.right}); }
});
this.prevstep = this.step;
} else {
this.pairs.forEach(p => {
api.setColor("black");
api.drawCurve(p.c1, offset);
api.drawCurve(p.c2, offset);
api.setColor("red");
api.drawbbox(p.c1.bbox(), offset);
api.setColor("blue");
api.drawbbox(p.c2.bbox(), offset);
});
}
if (this.pairs.length === 0) {
this.pairReset();
this.draw(api, curves);
}
// next panel: results
offset.x += w;
api.setColor("black");
api.drawLine({x:0,y:0}, {x:0,y:h}, offset);
// get intersections as coordinates
var results = curves[0].intersects(curves[1]).map(s => {
var tvals = s.split('/').map(v => parseFloat(v));
return {t1: tvals[0], t2: tvals[1]};
});
// filter out likely duplicates
var curr = results[0], _, i, same = ((a,b) => abs(a.t1-b.t1) < 0.01 && abs(a.t2-b.t2) < 0.01);
for(i=1; i<results.length; i++) {
_ = results[i];
if (same(curr, _)) {
results.splice(i--,1);
} else { curr = _; }
}
api.setColor("lightblue");
api.drawCurve(curves[0], offset);
api.drawCurve(curves[1], offset);
api.setColor("blue");
results.forEach(tvals => {
api.drawCircle(curves[0].get(tvals.t1), 3, offset);
});
},
stepUp: function() {
this.step++;
this.api.redraw();
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -1524,24 +1524,31 @@ lli = function(line1, line2):
<section id="curveintersection"> <section id="curveintersection">
<h1><a href="#curveintersection">Curve/curve intersection</a></h1> <h1><a href="#curveintersection">Curve/curve intersection</a></h1>
<p>Using de Casteljau's algorithm to split the curve we can now implement curve/curve intersection finding using a "divide and conquer" technique:</p> <p>Using de Casteljau's algorithm to split the curve we can now implement curve/curve intersection finding using a "divide and conquer" technique:</p>
<ul> <ol>
<li>Take two curves <em>C<sub>1</sub></em> and <em>C<sub>2</sub></em>, and treat them as a pair.</li> <li>Take two curves <em>C<sub>1</sub></em> and <em>C<sub>2</sub></em>, and treat them as a pair.</li>
<li>If their bounding boxes overlap, split up each curve into two sub-curves</li> <li>If their bounding boxes overlap, split up each curve into two sub-curves</li>
<li>With <em>C<sub>1.1</sub></em>, <em>C<sub>1.2</sub></em>, <em>C<sub>2.1</sub></em> and <em>C<sub>2.2</sub></em>, form four new pairs (<em>C<sub>1.1</sub></em>,<em>C<sub>2.1</sub></em>), (<em>C<sub>1.1</sub></em>, <em>C<sub>2.2</sub></em>), (<em>C<sub>1.2</sub></em>,<em>C<sub>2.1</sub></em>), and (<em>C<sub>1.2</sub></em>,<em>C<sub>2.2</sub></em>).</li> <li>With <em>C<sub>1.1</sub></em>, <em>C<sub>1.2</sub></em>, <em>C<sub>2.1</sub></em> and <em>C<sub>2.2</sub></em>, form four new pairs (<em>C<sub>1.1</sub></em>,<em>C<sub>2.1</sub></em>), (<em>C<sub>1.1</sub></em>, <em>C<sub>2.2</sub></em>), (<em>C<sub>1.2</sub></em>,<em>C<sub>2.1</sub></em>), and (<em>C<sub>1.2</sub></em>,<em>C<sub>2.2</sub></em>).</li>
<li>For each pair, check whether their bounding boxes overlap.<ul> <li>For each pair, check whether their bounding boxes overlap.<ol>
<li>If their bounding boxes do not overlap, discard the pair, as there is no intersection between this pair of curves.</li> <li>If their bounding boxes do not overlap, discard the pair, as there is no intersection between this pair of curves.</li>
<li>If there <em>is</em> overlap, rerun all steps for this pair.</li> <li>If there <em>is</em> overlap, rerun all steps for this pair.</li>
</ul> </ol>
</li> </li>
<li>Once the sub-curves we form are so small that they effectively occupy sub-pixel areas, we consider an intersection found, noting that we might have a cluster of multiple intersections at the sub-pixel level, out of which we pick one to act as "found" <code>t</code> value (we can either throw all but one away, we can average the cluster's <code>t</code> values, or you can do something even more creative).</li> <li>Once the sub-curves we form are so small that they effectively occupy sub-pixel areas, we consider an intersection found, noting that we might have a cluster of multiple intersections at the sub-pixel level, out of which we pick one to act as "found" <code>t</code> value (we can either throw all but one away, we can average the cluster's <code>t</code> values, or you can do something even more creative).</li>
</ul> </ol>
<p>This algorithm will start with a single pair, "balloon" until it runs in parallel for a large number of potential sub-pairs, and then taper back down as it homes in on intersection coordinates, ending up with as many pairs as there are intersections.</p> <p>This algorithm will start with a single pair, "balloon" until it runs in parallel for a large number of potential sub-pairs, and then taper back down as it homes in on intersection coordinates, ending up with as many pairs as there are intersections.</p>
<p>The following graphic applies this algorithm to a pair of cubic curves, one step at a time, so you can see the algorithm in action. Click the button to run a single step in the algorithm, after setting up your curves in some creative arrangement. The algorithm resets once it's found a solution, so you can try this with lots of different curves (can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!)</p> <p>The following graphic applies this algorithm to a pair of cubic curves, one step at a time, so you can see the algorithm in action. Click the button to run a single step in the algorithm, after setting up your curves in some creative arrangement. You can also change the value that is used in step 5 to determine whether the curves are small enough. Manipulating the curves or changing the threshold will reset the algorithm, so you can try this with lots of different curves.</p>
<Graphic title="Curve/curve intersections" setup={this.setup} draw={this.draw}> <p>(can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!)</p>
<button onClick={this.stepUp}>advance one step</button> <graphics-element title="Curve/curve intersections" width="825" height="275" src="./chapters/curveintersection/curve-curve.js" >
</Graphic> <fallback-image>
<img width="825px" height="275px" src="images\chapters\curveintersection\a71619a14589851390cf88aa07042d3e.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</fallback-image>
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
<button class="next">Advance one step</button>
<button class="reset">Reset</button>
</graphics-element>
<p>Self-intersection is dealt with in the same way, except we turn a curve into two or more curves first based on the inflection points. We then form all possible curve pairs with the resultant segments, and run exactly the same algorithm. All non-overlapping curve pairs will be removed after the first iteration, and the remaining steps home in on the curve's self-intersection points.</p> <p>Finding self-intersections is effectively the same procedure, except that we're starting with a single curve, so we need to turn that into two separate curves first. This is trivially achieved by splitting at an inflection point, or if there are none, just splitting at <code>t=0.5</code> first, and then running the exact same algorithm as above, with all non-overlapping curve pairs getting removed at each iteration, and each successive step homing in on the curve's self-intersection points.</p>
</section> </section>
<section id="abc"> <section id="abc">

View File

@@ -1521,24 +1521,31 @@ lli = function(line1, line2):
<section id="curveintersection"> <section id="curveintersection">
<h1><a href="ja-JP/index.html#curveintersection">Curve/curve intersection</a></h1> <h1><a href="ja-JP/index.html#curveintersection">Curve/curve intersection</a></h1>
<p>Using de Casteljau's algorithm to split the curve we can now implement curve/curve intersection finding using a "divide and conquer" technique:</p> <p>Using de Casteljau's algorithm to split the curve we can now implement curve/curve intersection finding using a "divide and conquer" technique:</p>
<ul> <ol>
<li>Take two curves <em>C<sub>1</sub></em> and <em>C<sub>2</sub></em>, and treat them as a pair.</li> <li>Take two curves <em>C<sub>1</sub></em> and <em>C<sub>2</sub></em>, and treat them as a pair.</li>
<li>If their bounding boxes overlap, split up each curve into two sub-curves</li> <li>If their bounding boxes overlap, split up each curve into two sub-curves</li>
<li>With <em>C<sub>1.1</sub></em>, <em>C<sub>1.2</sub></em>, <em>C<sub>2.1</sub></em> and <em>C<sub>2.2</sub></em>, form four new pairs (<em>C<sub>1.1</sub></em>,<em>C<sub>2.1</sub></em>), (<em>C<sub>1.1</sub></em>, <em>C<sub>2.2</sub></em>), (<em>C<sub>1.2</sub></em>,<em>C<sub>2.1</sub></em>), and (<em>C<sub>1.2</sub></em>,<em>C<sub>2.2</sub></em>).</li> <li>With <em>C<sub>1.1</sub></em>, <em>C<sub>1.2</sub></em>, <em>C<sub>2.1</sub></em> and <em>C<sub>2.2</sub></em>, form four new pairs (<em>C<sub>1.1</sub></em>,<em>C<sub>2.1</sub></em>), (<em>C<sub>1.1</sub></em>, <em>C<sub>2.2</sub></em>), (<em>C<sub>1.2</sub></em>,<em>C<sub>2.1</sub></em>), and (<em>C<sub>1.2</sub></em>,<em>C<sub>2.2</sub></em>).</li>
<li>For each pair, check whether their bounding boxes overlap.<ul> <li>For each pair, check whether their bounding boxes overlap.<ol>
<li>If their bounding boxes do not overlap, discard the pair, as there is no intersection between this pair of curves.</li> <li>If their bounding boxes do not overlap, discard the pair, as there is no intersection between this pair of curves.</li>
<li>If there <em>is</em> overlap, rerun all steps for this pair.</li> <li>If there <em>is</em> overlap, rerun all steps for this pair.</li>
</ul> </ol>
</li> </li>
<li>Once the sub-curves we form are so small that they effectively occupy sub-pixel areas, we consider an intersection found, noting that we might have a cluster of multiple intersections at the sub-pixel level, out of which we pick one to act as "found" <code>t</code> value (we can either throw all but one away, we can average the cluster's <code>t</code> values, or you can do something even more creative).</li> <li>Once the sub-curves we form are so small that they effectively occupy sub-pixel areas, we consider an intersection found, noting that we might have a cluster of multiple intersections at the sub-pixel level, out of which we pick one to act as "found" <code>t</code> value (we can either throw all but one away, we can average the cluster's <code>t</code> values, or you can do something even more creative).</li>
</ul> </ol>
<p>This algorithm will start with a single pair, "balloon" until it runs in parallel for a large number of potential sub-pairs, and then taper back down as it homes in on intersection coordinates, ending up with as many pairs as there are intersections.</p> <p>This algorithm will start with a single pair, "balloon" until it runs in parallel for a large number of potential sub-pairs, and then taper back down as it homes in on intersection coordinates, ending up with as many pairs as there are intersections.</p>
<p>The following graphic applies this algorithm to a pair of cubic curves, one step at a time, so you can see the algorithm in action. Click the button to run a single step in the algorithm, after setting up your curves in some creative arrangement. The algorithm resets once it's found a solution, so you can try this with lots of different curves (can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!)</p> <p>The following graphic applies this algorithm to a pair of cubic curves, one step at a time, so you can see the algorithm in action. Click the button to run a single step in the algorithm, after setting up your curves in some creative arrangement. You can also change the value that is used in step 5 to determine whether the curves are small enough. Manipulating the curves or changing the threshold will reset the algorithm, so you can try this with lots of different curves.</p>
<Graphic title="Curve/curve intersections" setup={this.setup} draw={this.draw}> <p>(can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!)</p>
<button onClick={this.stepUp}>advance one step</button> <graphics-element title="Curve/curve intersections" width="825" height="275" src="./chapters/curveintersection/curve-curve.js" >
</Graphic> <fallback-image>
<img width="825px" height="275px" src="images\chapters\curveintersection\a71619a14589851390cf88aa07042d3e.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</fallback-image>
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
<button class="next">Advance one step</button>
<button class="reset">Reset</button>
</graphics-element>
<p>Self-intersection is dealt with in the same way, except we turn a curve into two or more curves first based on the inflection points. We then form all possible curve pairs with the resultant segments, and run exactly the same algorithm. All non-overlapping curve pairs will be removed after the first iteration, and the remaining steps home in on the curve's self-intersection points.</p> <p>Finding self-intersections is effectively the same procedure, except that we're starting with a single curve, so we need to turn that into two separate curves first. This is trivially achieved by splitting at an inflection point, or if there are none, just splitting at <code>t=0.5</code> first, and then running the exact same algorithm as above, with all non-overlapping curve pairs getting removed at each iteration, and each successive step homing in on the curve's self-intersection points.</p>
</section> </section>
<section id="abc"> <section id="abc">

View File

@@ -123,15 +123,29 @@ class GraphicsAPI extends BaseAPI {
this.currentPoint = false; this.currentPoint = false;
} }
resetMovable(points) { resetMovable(...allpoints) {
this.movable.splice(0, this.movable.length); this.movable.splice(0, this.movable.length);
if (points) this.setMovable(points); if (allpoints) this.setMovable(...allpoints);
} }
setMovable(...allpoints) { setMovable(...allpoints) {
allpoints.forEach((points) => points.forEach((p) => this.movable.push(p))); allpoints.forEach((points) => points.forEach((p) => this.movable.push(p)));
} }
/**
* Multi-panel graphics: set panel count
*/
setPanelCount(c) {
this.panelWidth = this.width / c;
}
/**
* Multi-panel graphics: set up (0,0) to the next panel's start
*/
nextPanel(c) {
this.translate(this.panelWidth, 0);
}
/** /**
* Set up a slider to control a named, numerical property in the sketch. * Set up a slider to control a named, numerical property in the sketch.
* *
@@ -156,11 +170,13 @@ class GraphicsAPI extends BaseAPI {
slider.value = initial; slider.value = initial;
this[propname] = parseFloat(slider.value); this[propname] = parseFloat(slider.value);
let handlerName = `on${propname[0].toUpperCase()}${propname
.substring(1)
.toLowerCase()}`;
slider.listen(`input`, (evt) => { slider.listen(`input`, (evt) => {
this[propname] = parseFloat(evt.target.value); this[propname] = parseFloat(evt.target.value);
if (redraw && !this.redrawing) { if (this[handlerName]) this[handlerName](this[propname]);
this.redraw(); if (redraw && !this.redrawing) this.redraw();
}
}); });
return slider; return slider;

View File

@@ -177,6 +177,20 @@ class Bezier extends Original {
} }
ctx.restoreStyle(); ctx.restoreStyle();
} }
drawBoundingBox(color) {
let bbox = this.bbox(),
mx = bbox.x.min,
my = bbox.y.min,
MX = bbox.x.max,
MY = bbox.y.max,
api = this.api;
api.cacheStyle();
api.noFill();
api.setStroke(color ? color : `black`);
api.rect(mx, my, MX - mx, MY - my);
api.restoreStyle();
}
} }
export { Bezier }; export { Bezier };

View File

@@ -805,7 +805,7 @@ const utils = {
]; ];
} }
const cc1 = c1.split(0.5), let cc1 = c1.split(0.5),
cc2 = c2.split(0.5), cc2 = c2.split(0.5),
pairs = [ pairs = [
{ left: cc1.left, right: cc2.left }, { left: cc1.left, right: cc2.left },
@@ -818,7 +818,7 @@ const utils = {
return utils.bboxoverlap(pair.left.bbox(), pair.right.bbox()); return utils.bboxoverlap(pair.left.bbox(), pair.right.bbox());
}); });
const results = []; let results = [];
if (pairs.length === 0) return results; if (pairs.length === 0) return results;

View File

@@ -1515,24 +1515,31 @@ lli = function(line1, line2):
<section id="curveintersection"> <section id="curveintersection">
<h1><a href="zh-CN/index.html#curveintersection">Curve/curve intersection</a></h1> <h1><a href="zh-CN/index.html#curveintersection">Curve/curve intersection</a></h1>
<p>Using de Casteljau's algorithm to split the curve we can now implement curve/curve intersection finding using a "divide and conquer" technique:</p> <p>Using de Casteljau's algorithm to split the curve we can now implement curve/curve intersection finding using a "divide and conquer" technique:</p>
<ul> <ol>
<li>Take two curves <em>C<sub>1</sub></em> and <em>C<sub>2</sub></em>, and treat them as a pair.</li> <li>Take two curves <em>C<sub>1</sub></em> and <em>C<sub>2</sub></em>, and treat them as a pair.</li>
<li>If their bounding boxes overlap, split up each curve into two sub-curves</li> <li>If their bounding boxes overlap, split up each curve into two sub-curves</li>
<li>With <em>C<sub>1.1</sub></em>, <em>C<sub>1.2</sub></em>, <em>C<sub>2.1</sub></em> and <em>C<sub>2.2</sub></em>, form four new pairs (<em>C<sub>1.1</sub></em>,<em>C<sub>2.1</sub></em>), (<em>C<sub>1.1</sub></em>, <em>C<sub>2.2</sub></em>), (<em>C<sub>1.2</sub></em>,<em>C<sub>2.1</sub></em>), and (<em>C<sub>1.2</sub></em>,<em>C<sub>2.2</sub></em>).</li> <li>With <em>C<sub>1.1</sub></em>, <em>C<sub>1.2</sub></em>, <em>C<sub>2.1</sub></em> and <em>C<sub>2.2</sub></em>, form four new pairs (<em>C<sub>1.1</sub></em>,<em>C<sub>2.1</sub></em>), (<em>C<sub>1.1</sub></em>, <em>C<sub>2.2</sub></em>), (<em>C<sub>1.2</sub></em>,<em>C<sub>2.1</sub></em>), and (<em>C<sub>1.2</sub></em>,<em>C<sub>2.2</sub></em>).</li>
<li>For each pair, check whether their bounding boxes overlap.<ul> <li>For each pair, check whether their bounding boxes overlap.<ol>
<li>If their bounding boxes do not overlap, discard the pair, as there is no intersection between this pair of curves.</li> <li>If their bounding boxes do not overlap, discard the pair, as there is no intersection between this pair of curves.</li>
<li>If there <em>is</em> overlap, rerun all steps for this pair.</li> <li>If there <em>is</em> overlap, rerun all steps for this pair.</li>
</ul> </ol>
</li> </li>
<li>Once the sub-curves we form are so small that they effectively occupy sub-pixel areas, we consider an intersection found, noting that we might have a cluster of multiple intersections at the sub-pixel level, out of which we pick one to act as "found" <code>t</code> value (we can either throw all but one away, we can average the cluster's <code>t</code> values, or you can do something even more creative).</li> <li>Once the sub-curves we form are so small that they effectively occupy sub-pixel areas, we consider an intersection found, noting that we might have a cluster of multiple intersections at the sub-pixel level, out of which we pick one to act as "found" <code>t</code> value (we can either throw all but one away, we can average the cluster's <code>t</code> values, or you can do something even more creative).</li>
</ul> </ol>
<p>This algorithm will start with a single pair, "balloon" until it runs in parallel for a large number of potential sub-pairs, and then taper back down as it homes in on intersection coordinates, ending up with as many pairs as there are intersections.</p> <p>This algorithm will start with a single pair, "balloon" until it runs in parallel for a large number of potential sub-pairs, and then taper back down as it homes in on intersection coordinates, ending up with as many pairs as there are intersections.</p>
<p>The following graphic applies this algorithm to a pair of cubic curves, one step at a time, so you can see the algorithm in action. Click the button to run a single step in the algorithm, after setting up your curves in some creative arrangement. The algorithm resets once it's found a solution, so you can try this with lots of different curves (can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!)</p> <p>The following graphic applies this algorithm to a pair of cubic curves, one step at a time, so you can see the algorithm in action. Click the button to run a single step in the algorithm, after setting up your curves in some creative arrangement. You can also change the value that is used in step 5 to determine whether the curves are small enough. Manipulating the curves or changing the threshold will reset the algorithm, so you can try this with lots of different curves.</p>
<Graphic title="Curve/curve intersections" setup={this.setup} draw={this.draw}> <p>(can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!)</p>
<button onClick={this.stepUp}>advance one step</button> <graphics-element title="Curve/curve intersections" width="825" height="275" src="./chapters/curveintersection/curve-curve.js" >
</Graphic> <fallback-image>
<img width="825px" height="275px" src="images\chapters\curveintersection\a71619a14589851390cf88aa07042d3e.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</fallback-image>
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
<button class="next">Advance one step</button>
<button class="reset">Reset</button>
</graphics-element>
<p>Self-intersection is dealt with in the same way, except we turn a curve into two or more curves first based on the inflection points. We then form all possible curve pairs with the resultant segments, and run exactly the same algorithm. All non-overlapping curve pairs will be removed after the first iteration, and the remaining steps home in on the curve's self-intersection points.</p> <p>Finding self-intersections is effectively the same procedure, except that we're starting with a single curve, so we need to turn that into two separate curves first. This is trivially achieved by splitting at an inflection point, or if there are none, just splitting at <code>t=0.5</code> first, and then running the exact same algorithm as above, with all non-overlapping curve pairs getting removed at each iteration, and each successive step homing in on the curve's self-intersection points.</p>
</section> </section>
<section id="abc"> <section id="abc">