mirror of
https://github.com/Pomax/BezierInfo-2.git
synced 2025-08-30 19:50:01 +02:00
offsetting
This commit is contained in:
@@ -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.
|
||||
|
@@ -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]);
|
||||
}
|
||||
};
|
118
docs/chapters/offsetting/offsetting.js
Normal file
118
docs/chapters/offsetting/offsetting.js
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user