1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-30 19:50:01 +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

@@ -57,9 +57,14 @@ So, you cannot offset a Bézier curve perfectly with another Bézier curve, no m
A good way to do this reduction is to first find the curve's extreme points, as explained in the earlier section on curve extremities, and use these as initial splitting points. After this initial split, we can check each individual segment to see if it's "safe enough" based on where the center of the curve is. If the on-curve point for `t=0.5` is too far off from the center, we simply split the segment down the middle. Generally this is more than enough to end up with safe segments.
The following graphics show off curve offsetting, and you can use your up and down arrow keys to control the distance at which the curve gets offset. The curve first gets reduced to safe segments, each of which is then offset at the desired distance. Especially for simple curves, particularly easily set up for quadratic curves, no reduction is necessary, but the more twisty the curve gets, the more the curve needs to be reduced in order to get segments that can safely be scaled.
The following graphics show off curve offsetting, and you can use the slider to control the distance at which the curve gets offset. The curve first gets reduced to safe segments, each of which is then offset at the desired distance. Especially for simple curves, particularly easily set up for quadratic curves, no reduction is necessary, but the more twisty the curve gets, the more the curve needs to be reduced in order to get segments that can safely be scaled.
<Graphic title="Offsetting a quadratic Bézier curve" setup={this.setupQuadratic} draw={this.draw} onKeyDown={this.props.onKeyDown} />
<Graphic title="Offsetting a cubic Bézier curve" setup={this.setupCubic} draw={this.draw} onKeyDown={this.props.onKeyDown} />
<graphics-element title="Offsetting a quadratic Bézier curve" src="./offsetting.js" data-type="quadratic">
<input type="range" min="5" max="50" step="1" value="20" class="slide-control">
</graphics-element>
<graphics-element title="Offsetting a cubic Bézier curve" src="./offsetting.js" data-type="cubic">
<input type="range" min="5" max="50" step="1" value="20" class="slide-control">
</graphics-element>
You may notice that this may still lead to small 'jumps' in the sub-curves when moving the curve around. This is caused by the fact that we're still performing a naive form of offsetting, moving the control points the same distance as the start and end points. If the curve is large enough, this may still lead to incorrect offsets.

View File

@@ -1,58 +0,0 @@
module.exports = {
statics: {
keyHandlingOptions: {
propName: "distance",
values: {
"38": 1, // up arrow
"40": -1 // down arrow
}
}
},
setup: function(api, curve) {
api.setCurve(curve);
api.distance = 20;
},
setupQuadratic: function(api) {
var curve = api.getDefaultQuadratic();
this.setup(api, curve);
},
setupCubic: function(api) {
var curve = api.getDefaultCubic();
this.setup(api, curve);
},
draw: function(api, curve) {
api.reset();
api.drawSkeleton(curve);
var reduced = curve.reduce();
reduced.forEach(c => {
api.setRandomColor();
api.drawCurve(c);
api.drawCircle(c.points[0], 1);
});
var last = reduced.slice(-1)[0];
api.drawPoint(last.points[3] || last.points[2]);
api.setColor("red");
var offset = curve.offset(api.distance);
offset.forEach(c => {
api.drawPoint(c.points[0]);
api.drawCurve(c);
});
last = offset.slice(-1)[0];
api.drawPoint(last.points[3] || last.points[2]);
api.setColor("blue");
offset = curve.offset(-api.distance);
offset.forEach(c => {
api.drawPoint(c.points[0]);
api.drawCurve(c);
});
last = offset.slice(-1)[0];
api.drawPoint(last.points[3] || last.points[2]);
}
};

View File

@@ -0,0 +1,118 @@
let curve;
setup() {
const type = this.parameters.type ?? `quadratic`;
if (type === `quadratic`) {
curve = Bezier.defaultQuadratic(this);
} else {
curve = Bezier.defaultCubic(this);
}
setMovable(curve.points);
setSlider(`.slide-control`, `distance`, 20);
}
draw() {
clear();
noFill();
curve.drawSkeleton();
var reduced = this.reduce(curve);
reduced.forEach(c => {
setStroke( randomColor() );
this.drawCurve(c);
circle(c.points[0].x, c.points[0].y, 2);
});
var last = reduced.slice(-1)[0];
let p = last.points[3] ?? last.points[2];
circle(p.x, p.y, 3);
setStroke(`#FF000050`);
var offset = curve.offset(this.distance);
offset.forEach(c => {
circle(c.points[0].x, c.points[0].y, 2);
this.drawCurve(c);
});
last = offset.slice(-1)[0];
p = last.points[3] ?? last.points[2];
circle(p.x, p.y, 3);
setStroke(`#0000FF50`);
var offset = curve.offset(-this.distance);
offset.forEach(c => {
circle(c.points[0].x, c.points[0].y, 2);
this.drawCurve(c);
});
last = offset.slice(-1)[0];
p = last.points[3] ?? last.points[2];
circle(p.x, p.y, 3);
curve.drawPoints();
}
drawCurve(c) {
start()
c.getLUT(16).forEach(p => vertex(p.x, p.y));
end();
}
reduce(curve) {
let i,
t1 = 0,
t2 = 0,
step = 0.01,
segment,
pass1 = [],
pass2 = [];
// first pass: split on extrema
let extrema = curve.extrema().values;
if (extrema.indexOf(0) === -1) {
extrema = [0].concat(extrema);
}
if (extrema.indexOf(1) === -1) {
extrema.push(1);
}
for (t1 = extrema[0], i = 1; i < extrema.length; i++) {
t2 = extrema[i];
segment = curve.split(t1, t2);
segment._t1 = t1;
segment._t2 = t2;
pass1.push(segment);
t1 = t2;
}
// second pass: further reduce these segments to simple segments
pass1.forEach(p1 => {
t1 = 0;
t2 = 0;
while (t2 <= 1) {
for (t2 = t1 + step; t2 <= 1 + step; t2 += step) {
segment = p1.split(t1, t2);
if (!segment.simple()) {
t2 -= step;
if ( abs(t1 - t2) < step) {
// we can never form a reduction
return [];
}
segment = p1.split(t1, t2);
segment._t1 = map(t1, 0, 1, p1._t1, p1._t2);
segment._t2 = map(t2, 0, 1, p1._t1, p1._t2);
pass2.push(segment);
t1 = t2;
break;
}
}
}
if (t1 < 1) {
segment = p1.split(t1, 1);
segment._t1 = map(t1, 0, 1, p1._t1, p1._t2);
segment._t2 = p1._t2;
pass2.push(segment);
}
});
return pass2;
}