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

all sections ported

This commit is contained in:
Pomax
2016-01-10 14:59:59 -08:00
parent 3c190e630c
commit ab76ec800d
13 changed files with 40289 additions and 281 deletions

View File

@@ -41,10 +41,7 @@ module.exports = {
polybezier: require("./polybezier"),
/*
// This section is way too much work to port, and not worth implementing given paper.js etc.
shapes: require("./shapes"), // Boolean shape operations
*/
shapes: require("./shapes"),
projections: require("./projections"),
offsetting: require("./offsetting"),

View File

@@ -0,0 +1,178 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Shapes = React.createClass({
getDefaultProps: function() {
return {
title: "Boolean shape operations"
};
},
formPath: function(api, mx, my, w, h) {
mx = mx || 0;
my = my || 0;
var unit = 30;
var unit2 = unit/2;
w = w || 8 * unit;
h = h || 4 * unit;
var w2 = w/2;
var h2 = h/2;
var ow3 = w2/3;
var oh3 = h2/3;
var Paper = api.Paper;
var Path = Paper.Path;
var Point = Paper.Point;
var path = new Path();
path.moveTo(
new Point(mx-w2 + unit*2, my-h2)
);
path.cubicCurveTo(
new Point(mx-w2 + unit2, my-h2 + unit2),
new Point(mx-w2 + unit2, my+h2 - unit2),
new Point(mx-w2 + unit*2, my+h2)
);
path.cubicCurveTo(
new Point(mx-ow3, my+oh3),
new Point(mx+ow3, my+oh3),
new Point(mx+w2 - unit*2, my+h2)
);
path.cubicCurveTo(
new Point(mx+w2 - unit2, my+h2 - unit2),
new Point(mx+w2 - unit2, my-h2 + unit2),
new Point(mx+w2 - unit*2, my-h2)
);
path.cubicCurveTo(
new Point(mx+ow3, my-oh3),
new Point(mx-ow3, my-oh3),
new Point(mx-w2 + unit*2, my-h2)
);
path.closePath(true);
return path;
},
setup: function(api) {
var dim = api.getPanelWidth();
var pad = 40;
var cx = dim/2;
var cy = dim/2;
api.c1 = this.formPath(api, cx, cy);
cx += pad;
cy += pad;
api.c2 = this.formPath(api, cx, cy);
},
onMouseMove: function(evt, api) {
var cx = evt.offsetX;
var cy = evt.offsetY;
api.c2.remove();
api.c2 = this.formPath(api, cx, cy);
},
draw: function(api) {
api.project.clear();
var c1 = api.c1,
c2 = api.c2,
c3 = c1.unite(c2);
api.project.activeLayer.addChildren([c1,c2,c3]);
c1.strokeColor = "rgb(100,100,255)";
c2.strokeColor = "rgb(100,100,255)";
c3.strokeColor = "red";
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>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)</p>
<p>The procedure for performing boolean operations consists, broadly, of four steps:</p>
<ol>
<li>Find the intersection points between both shapes,</li>
<li>cut up the shapes into multiple sections between these intersections,</li>
<li>discard any section that isn't part of the desired operation's resultant shape, and</li>
<li>link up the remaining sections to form the new shape.</li>
</ol>
<p>Finding all intersections between two poly-Bézier curves, or any poly-line-section shape,
is similar to the iterative algorithm discussed in the section on curve/curve intersection.
For each segment in the poly-Bézier curve we check whether its bounding box overlaps with
any of the segment bounding boxes in the other poly-Bézier curve. If so, we run normal
intersection detection.</p>
<p>After we found all intersection points, we split up our poly-Bézier curves, making sure to
record which of the newly formed poly-Bézier curves might potentially link up at the points
we split the originals up at. This will let us quickly glue poly-Bézier curves back together
after the next step.</p>
<p>Once we have all the new poly-Bézier curves, we run the first step of the desired boolean
operation.</p>
<ul>
<li>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.</li>
<li>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.</li>
<li>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.</li>
</ul>
<table className="sketch"><tbody><tr><td className="labeled-image">
<img src="images/op_base.gif" height="169px"/>
<p>Two overlapping shapes.</p>
</td><td className="labeled-image">
<img src="images/op_union.gif" height="169px"/>
<p>The unified region.</p>
</td><td className="labeled-image">
<img src="images/op_intersection.gif" height="169px"/>
<p>Their intersection.</p>
</td><td className="labeled-image">
<img src="images/op_exclusion.gif" height="169px"/>
<p>Their exclusion regions.</p>
</td></tr></tbody></table>
<p>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 <a href="http://folk.uio.no/bjornw/doc/bifrost-ref/bifrost-ref-12.html">even-odd
rule</a>: 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).</p>
<p>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 <a href="http://paperjs.org">Paper.js</a> 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.</p>
<p>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, with segments that lie inside each other's shapes
marked light blue, and segments that exist outside of the other shape marked in red.</p>
<Graphic preset="simple" title="Boolean shape operations with Paper.js" paperjs={true}
setup={this.setup} draw={this.draw} onMouseMove={this.onMouseMove}/>
</section>
);
}
});
module.exports = Shapes;