Skip to content

Instantly share code, notes, and snippets.

@mutoo
Last active July 16, 2019 23:47
Show Gist options
  • Select an option

  • Save mutoo/f9f4e66008804c816f3b54fb16e4a6a2 to your computer and use it in GitHub Desktop.

Select an option

Save mutoo/f9f4e66008804c816f3b54fb16e4a6a2 to your computer and use it in GitHub Desktop.
A closed uniform BSpline curve
<div id="app">
<div class="container" ref="container"></div>
</div>
const INITIAL_POINTS = [
[68, 83],
[32, 320],
[272, 375],
[396, 22],
[563, 72],
[439, 317]];
// The uniform bspline matrix
// http://graphics.cs.ucdavis.edu/~joy/ecs178/Unit-7-Notes/MatrixBSpline.pdf
const UNIFORM_BSPLINE_MATRIX = math.matrix([
[1 / 6, 4 / 6, 1 / 6, 0],
[-3 / 6, 0, 3 / 6, 0],
[3 / 6, -6 / 6, 3 / 6, 0],
[-1 / 6, 3 / 6, -3 / 6, 1 / 6],
]);
let STEP_PER_SEGMENT = 6;
new Vue({
el: '#app',
mounted() {
this.setup();
this.createHandleDef();
let updateStage = () => {
this.stage.clear();
if (this.points.length < 4) return;
let points = this.points.map((p) => {
return [p.x(), p.y()];
});
points.push(points[0]); // close the control polygon
this.stage.polyline(points).fill('none').stroke({width: 1});
points.push(points[1]); // to close the b spline curve
points.push(points[2]); // requires more points
// reference: https://pages.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/B-spline/bspline-curve-closed.html
let i = 4;
let controlPoints = points.slice(0, i);
do {
let line = [];
let controlMatrix = math.multiply(UNIFORM_BSPLINE_MATRIX,
controlPoints);
for (let i = 0; i <= 1; i += 1 / STEP_PER_SEGMENT) {
let ts = [1, i, i * i, i * i * i];
let p = math.multiply(ts, controlMatrix);
line.push(p.toArray());
}
this.stage.polyline(line).
fill('none').
stroke({width: 2}).
addClass(i % 2 ? 'segment--a' : 'segment--b');
controlPoints.shift();
controlPoints.push(points[i]);
} while (i++ < points.length);
};
this.points = INITIAL_POINTS.map((p) => {
return this.createHandle(p[0], p[1], updateStage);
});
updateStage();
},
methods: {
setup() {
this.canvas = SVG(this.$refs['container']).size(640, 480);
this.canvas.rect(640, 480).fill('none').stroke({width: 1});
this.stage = this.canvas.group();
},
createHandleDef() {
let defs = this.canvas.defs();
this.handleDef = defs.group().addClass('handle');
// make it easy to move on touch screen;
this.handleDef.rect(40, 40).fill('transparent').translate(-20, -20);
this.handleDef.rect(10, 10).fill('black').translate(-5, -5);
},
createHandle(x, y, callback) {
let handle = this.canvas.use(this.handleDef).x(x).y(y);
let hammer = new Hammer(handle.node);
let handleX, handleY;
hammer.on('panstart', (ev) => {
handleX = handle.x();
handleY = handle.y();
});
hammer.on('panmove', (ev) => {
handle.x(handleX + ev.deltaX);
handle.y(handleY + ev.deltaY);
callback();
});
hammer.on('panend', (ev) => {
});
return handle;
},
},
});
<script src="//unpkg.com/svg.js"></script>
<script src="//unpkg.com/hammerjs"></script>
<script src="//unpkg.com/mathjs/dist/math.js"></script>
<script src="//unpkg.com/vue"></script>
.handle {
cursor: move;
}
.segment--a {
stroke: red;
}
.segment--b {
stroke: blue;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment