1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-29 19:20:39 +02:00

offsetting

This commit is contained in:
Pomax
2020-09-04 22:01:35 -07:00
parent 83dcab57cb
commit 7e5c6e2eba
38 changed files with 15520 additions and 412 deletions

View File

@@ -0,0 +1,133 @@
let shape1, shape2, ox=50, oy=50;
setup() {
let d = 30;
let p = [
{ x: d*2, y: d*1}, { x: d*1, y: d*2}, { x: d*1, y: d*4},
{ x: d*2, y: d*5}, { x: d*3, y: d*4}, { x: d*5, y: d*4},
{ x: d*6, y: d*5}, { x: d*7, y: d*4}, { x: d*7, y: d*2},
{ x: d*6, y: d*1}, { x: d*5, y: d*2}, { x: d*3, y: d*2},
]
shape1 = [
new Bezier(this, p[0].x, p[0].y, p[1].x, p[1].y, p[2].x, p[2].y, p[3].x, p[3].y),
new Bezier(this, p[3].x, p[3].y, p[4].x, p[4].y, p[5].x, p[5].y, p[6].x, p[6].y),
new Bezier(this, p[6].x, p[6].y, p[7].x, p[7].y, p[8].x, p[8].y, p[9].x, p[9].y),
new Bezier(this, p[9].x, p[9].y, p[10].x, p[10].y, p[11].x, p[11].y, p[0].x, p[0].y),
];
shape2 = [
new Bezier(this, p[0].x, p[0].y, p[1].x, p[1].y, p[2].x, p[2].y, p[3].x, p[3].y),
new Bezier(this, p[3].x, p[3].y, p[4].x, p[4].y, p[5].x, p[5].y, p[6].x, p[6].y),
new Bezier(this, p[6].x, p[6].y, p[7].x, p[7].y, p[8].x, p[8].y, p[9].x, p[9].y),
new Bezier(this, p[9].x, p[9].y, p[10].x, p[10].y, p[11].x, p[11].y, p[0].x, p[0].y),
];
// link the curves into paths
shape1.forEach((s, i) => (s.next = shape1[(i+1) % shape1.length]));
shape2.forEach((s, i) => (s.next = shape2[(i+1) % shape2.length]));
shape2.forEach(s => s.points.forEach(p => { p.x += ox; p.y += oy; }));
}
draw() {
clear();
/*
noFill();
shape2.forEach(c => c.points.forEach(p => {
p.x -= ox;
p.y -= oy;
}));
if (this.cursor.down) {
ox += this.cursor.diff.x;
oy += this.cursor.diff.y;
}
shape2.forEach(c => c.points.forEach(p => {
p.x += ox;
p.y += oy;
}));
shape1.forEach(curve => this.drawSegment(curve));
shape2.forEach(curve => this.drawSegment(curve));
this.drawBoolean(shape1, shape2);
*/
}
drawSegment(segment) {
setStroke( randomColor(0.2) );
start()
segment.getLUT(16).forEach(p => vertex(p.x, p.y))
end();
}
drawBoolean(s1, s2) {
let intersections = [];
// We're going to test for intersections in an *incredibly* naive fashion,
// simply checking each curve against each other curve. Normally you'd first
// throw way all curves that can't intersect, using bounding box checks or
// oct-tree space reduction, etc.
s1.forEach(curve1 => {
s2.forEach(curve2 => {
let ti = curve1.intersects(curve2);
if (ti.length) {
ti = ti.map(s => s.split(`/`).map(parseFloat));
// remove "near enough to be considered duplicate" t pairs
for(let i=ti.length-1; i>0; i--) {
if ( abs(ti[i][0] - ti[i-1][0]) < 0.01 && abs(ti[i][1] - ti[i-1][1]) < 0.01) {
ti.splice(i,1);
}
}
// store this curve pair with intersection t value(s)
intersections.push({ curve1, curve2, ti })
}
});
});
intersections.forEach(i => {
const { curve1, curve2, ti } = i;
ti.forEach(t => {
let c1 = curve1.get(t[0]); circle(c1.x, c1.y, 3);
let c2 = curve2.get(t[1]); circle(c2.x, c2.y, 3);
});
});
let nodes = [...s1, ...s2];
let sequence = [];
let node = nodes[0];
for(let i=0; i<2; i++) {
let entry = intersections.find(v => v.curve1 === node || v.curve2 === node);
if (entry) {
const { curve1, curve2, ti } = entry;
console.log(ti);
let c1, c2;
c1 = curve1.split(t[0]);
c2 = curve2.split(t[1]);
// resolve the intersections
console.log(`resolve `,entry);
sequence.push(node);
node = node.next;
} else {
sequence.push(node);
node = node.next;
}
}
sequence.forEach(c => c.drawCurve(`black`));
}
onMouseMove() {
if (this.cursor.down) {
redraw();
}
}

View File

@@ -1,3 +1,5 @@
# THIS SECTION IS CURRENTLY NOT PART OF THE MAIN DOCUMENT, AS IT DOES NOT ACTUALLY TEACH ANYTHING USEFUL.
# Boolean shape operations
We can apply the topics covered so far in this primer to effect boolean shape operations: getting the union, intersection, or exclusion, between two or more shapes that involve Bézier curves. For simplicity (well... sort of, more homogeneity), we'll be looking at poly-Bézier shapes only, but a shape that consists of a mix of lines and Bézier curves is technically a simplification. (Although it does mean we need to write a definition for the class of shapes that mix lines and Bézier curves. Since poly-Bézier curves are a superset, we'll be using those in the following examples.)
@@ -17,33 +19,32 @@ Once we have all the new poly-Bézier curves, we run the first step of the desir
- Union: discard all poly-Bézier curves that lie "inside" our union of our shapes. E.g. if we want the union of two overlapping circles, the resulting shape is the outline.
- Intersection: discard all poly-Bézier curves that lie "outside" the intersection of the two shapes. E.g. if we want the intersection of two overlapping circles, the resulting shape is the tapered ellipse where they overlap.
- Exclusion: none of the sections are discarded, but we will need to link the shapes back up in a special way. Flip any section that would qualify for removal under UNION rules.
- Exclusion: none of the sections are discarded, but we will need to link the shapes back up in a special way. Flip any section that would qualify for removal under "union" rules.
<table class="sketch"><tbody><tr>
<td class="labeled-image">
<div class="grid">
<figure>
<img src="images/op_base.gif" height="169"/>
Two overlapping shapes.
</td>
<td class="labeled-image">
<figcaption>Two overlapping shapes</figcaption>
</figure>
<figure class="labeled-image">
<img src="images/op_union.gif" height="169"/>
The unified region.
</td>
<td class="labeled-image">
<figcaption>Their union</figcaption>
</figure>
<figure class="labeled-image">
<img src="images/op_intersection.gif" height="169"/>
Their intersection.
</td>
<td class="labeled-image">
<figcaption>Their intersection</figcaption>
</figure>
<figure class="labeled-image">
<img src="images/op_exclusion.gif" height="169"/>
Their exclusion regions.
</td>
</tr></tbody></table>
<figcaption>Their exclusion (union minus intersection)</figcaption>
</figure>
</div>
The main complication in the outlined procedure here is determining how sections qualify in terms of being "inside" and "outside" of our shapes. For this, we need to be able to perform point-in-shape detection, for which we'll use a classic algorithm: getting the "crossing number" by using ray casting, and then testing for "insidedness" by applying the [even-odd rule](http://folk.uio.no/bjornw/doc/bifrost-ref/bifrost-ref-12.html): For any point and any shape, we can cast a ray from our point, to some point that we know lies outside of the shape (such as a corner of our drawing surface). We then count how many times that line crosses our shape (remember that we can perform line/curve intersection detection quite easily). If the number of times it crosses the shape's outline is even, the point did not actually lie inside our shape. If the number of intersections is odd, our point did lie inside out shape. With that knowledge, we can decide whether to treat a section that such a point lies on "needs removal" (under union rules), "needs preserving" (under intersection rules), or "needs flipping" (under exclusion rules).
These operations are expensive, and implementing your own code for this is generally a bad idea if there is already a geometry package available for your language of choice. In this case, for JavaScript the most excellent [Paper.js](http://paperjs.org) already comes with all the code in place to perform efficient boolean shape operations, so rather that implement an inferior version here, I can strongly recommend the Paper.js library if you intend to do any boolean shape work.
The following graphic shows Paper.js doing its thing for two shapes: one static, and one that is linked to your mouse pointer. If you move the mouse around, you'll see how the shape intersections are resolved. The base shapes are outlined in blue, and the boolean result is coloured red.
(Of course, as a general geometry library, Paper.js is also roughly the size of this entire primer, so for illustrative purposes the following graphic implements its own boolean operations, and may not do quite the right thing on all edge cases!)
<graphics-element title="Boolean shape operations" src="./boolean.js"></graphics-element>
<Graphic title="Boolean shape operations with Paper.js" paperjs={true} setup={this.setup} draw={this.draw} onMouseMove={this.onMouseMove}>
<button onclick="() => this.setMode(mode)">mode</button>
</Graphic>

File diff suppressed because it is too large Load Diff