1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-22 16:23:12 +02:00
This commit is contained in:
Pomax
2016-09-14 14:55:46 -07:00
parent d7d1df1119
commit b9e1d711fd
15 changed files with 538 additions and 74 deletions

View File

@@ -18,6 +18,7 @@ var BSplineGraphic = React.createClass({
this.activeDistance = 9;
this.points = [];
this.knots = [];
this.weights = [];
this.nodes = [];
this.cp = undefined;
this.dx = undefined;
@@ -37,33 +38,55 @@ var BSplineGraphic = React.createClass({
return <canvas className="bspline-graphic" ref="sketch" />;
},
keydownlisten(e) { this.setKeyboardValues(e); this.keyDown(); },
keyuplisten(e) { this.setKeyboardValues(e); this.keyUp(); },
keypresslisten(e) { this.setKeyboardValues(e); this.keyPressed(); },
mousedownlisten(e) { this.setMouseValues(e); this.mouseDown(); },
mouseuplisten(e) { this.setMouseValues(e); this.mouseUp(); },
mousemovelisten(e) { this.setMouseValues(e); this.mouseMove(); if(this.isMouseDown && this.mouseDrag) { this.mouseDrag(); }},
wheellissten(e) { e.preventDefault(); this.scrolled(e.deltaY < 0 ? 1 : -1); },
componentDidMount() {
var cvs = this.cvs = this.refs.sketch;
// Keyboard event handling
cvs.addEventListener("keydown", (e) => { this.setKeyboardValues(e); if (typeof this.keyDown !== "undefined") { this.keyDown(); }});
cvs.addEventListener("keyup", (e) => { this.setKeyboardValues(e); if (typeof this.keyUp !== "undefined") { this.keyUp(); }});
cvs.addEventListener("keypress", (e) => { this.setKeyboardValues(e); if (typeof this.keyPressed !== "undefined") { this.keyPressed(); }});
cvs.addEventListener("keydown", this.keydownlisten);
cvs.addEventListener("keyup", this.keyuplisten);
cvs.addEventListener("keypress", this.keypresslisten);
// Mouse event handling
cvs.addEventListener("mousedown", (e) => { this.setMouseValues(e); if (typeof this.mouseDown !== "undefined") { this.mouseDown(); }});
cvs.addEventListener("mouseup", (e) => { this.setMouseValues(e); if (typeof this.mouseUp !== "undefined") { this.mouseUp(); }});
cvs.addEventListener("mousemove", (e) => { this.setMouseValues(e); if (typeof this.mouseMove !== "undefined") { this.mouseMove(); } if(this.isMouseDown && this.mouseDrag) { this.mouseDrag(); }});
cvs.addEventListener("mousedown", this.mousedownlisten);
cvs.addEventListener("mouseup", this.mouseuplisten);
cvs.addEventListener("mousemove", this.mousemovelisten);
// Scroll event handling
if (this.props.scrolling) { cvs.addEventListener("wheel", this.wheellissten); }
// Boom let's go
this.setup();
},
componentWillUnmount() {
var cvs = this.cvs = this.refs.sketch;
cvs.removeEventListener("keydown", this.keydownlisten);
cvs.removeEventListener("keyup", this.keyuplisten);
cvs.removeEventListener("keypress", this.keypresslisten);
cvs.removeEventListener("mousedown", this.mousedownlisten);
cvs.removeEventListener("mouseup", this.mouseuplisten);
cvs.removeEventListener("mousemove", this.mousemovelisten);
if (this.props.scrolling) { cvs.removeEventListener("wheel", this.wheellissten); }
},
// base API
drawCurve(points) {
points = points || this.points;
var ctx = this.ctx;
var weights = this.weights.length>0 ? this.weights : false;
ctx.beginPath();
var p = interpolate(0, this.degree, points, this.knots);
var p = interpolate(0, this.degree, points, this.knots, weights);
ctx.moveTo(p[0], p[1]);
for(let t=0.01; t<1; t+=0.01) {
p = interpolate(t, this.degree, points, this.knots);
p = interpolate(t, this.degree, points, this.knots, weights);
ctx.lineTo(p[0], p[1]);
}
p = interpolate(1, this.degree, points, this.knots);
p = interpolate(1, this.degree, points, this.knots, weights);
ctx.lineTo(p[0], p[1]);
ctx.stroke();
ctx.closePath();
@@ -71,11 +94,12 @@ var BSplineGraphic = React.createClass({
drawKnots(points) {
var knots = this.knots;
var weights = this.weights.length>0 ? this.weights : false;
knots.forEach((knot,i) => {
if (i < this.degree) return;
if (i > knots.length - 1 - this.degree) return;
var p = interpolate(knot, this.degree, points, knots, false, false, true);
this.ellipse(p[0], p[1], 3);
var p = interpolate(knot, this.degree, points, knots, weights, false, true);
this.circle(p[0], p[1], 3);
});
},
@@ -102,11 +126,11 @@ var BSplineGraphic = React.createClass({
i;
// form the open-uniform knot vector
for (i=1; i < l - this.degree; i++) { knots.push(i); }
for (i=1; i < l - this.degree; i++) { knots.push(i + this.degree); }
// add [degree] zeroes at the front
for (i=0; i <= this.degree; i++) { knots = [0].concat(knots); }
for (i=0; i <= this.degree; i++) { knots = [this.degree].concat(knots); }
// add [degree] max-values to the back
for (i=0; i <= this.degree; i++) { knots.push(m); }
for (i=0; i <= this.degree; i++) { knots.push(m + this.degree); }
return knots;
},
@@ -138,6 +162,12 @@ var BSplineGraphic = React.createClass({
return nodes;
},
formWeights(points) {
var weights = [];
points.forEach(p => weights.push(1));
return weights;
},
setDegree(d) {
this.degree += d;
this.knots = this.formKnots(this.points);
@@ -177,7 +207,7 @@ var BSplineGraphic = React.createClass({
keyUp() {
// ... do nothing?
},
keyPressed() {
// ... do nothing?
},
@@ -211,6 +241,28 @@ var BSplineGraphic = React.createClass({
// ... do nothing?
},
scrolled(direction) {
this.cp = this.getCurrentPoint(this.mouseX, this.mouseY);
if (!this.cp) return;
// base case
var pos = this.points.indexOf(this.cp);
if (this.weights.length>pos) {
this.weights[pos] += direction * 0.1;
if (this.weights[pos] < 0) {
this.weights[pos] = 0;
}
}
// possible multiplicity due to "closed" curves
pos = this.points.indexOf(this.cp, pos+1);
if (pos!==-1 && this.weights.length>pos) {
this.weights[pos] += direction * 0.1;
if (this.weights[pos] < 0) {
this.weights[pos] = 0;
}
}
this.redraw();
},
// keyboard events
setKeyboardValues(e) {
if (!e.ctrlKey && !e.metaKey && !e.altKey) {
@@ -226,7 +278,7 @@ var BSplineGraphic = React.createClass({
this.mouseX = e.clientX - brect.left;
this.mouseY = e.clientY - brect.top;
},
// API stuffs
size(w,h) {
@@ -250,7 +302,7 @@ var BSplineGraphic = React.createClass({
for(let y=spacing; y<this.height-1; y+=spacing) { this.line(0,y,this.width,y); }
},
ellipse(x,y,r) {
circle(x,y,r) {
let hr = r/2;
var ctx = this.ctx;
ctx.beginPath();
@@ -270,6 +322,9 @@ var BSplineGraphic = React.createClass({
},
stroke(r,g,b,a) {
if (typeof r === "string") {
return (this.ctx.strokeStyle = r);
}
if (g===undefined) { g=r; b=r; }
if (a===undefined) { a = 1; }
this.ctx.strokeStyle = this.rgba(r,g,b,a);
@@ -278,6 +333,9 @@ var BSplineGraphic = React.createClass({
noStroke() { this.ctx.strokeStyle = "none"; },
fill(r,g,b,a) {
if (typeof r === "string") {
return (this.ctx.fillStyle = r);
}
if (g===undefined) { g=r; b=r; }
if (a===undefined) { a = 1; }
this.ctx.fillStyle = this.rgba(r,g,b,a);
@@ -290,7 +348,7 @@ var BSplineGraphic = React.createClass({
beginPath() {
this.ctx.beginPath(); this.bp = true;
},
vertex(x,y) {
if (!this.bp) {
return this.ctx.lineTo(x,y);

View File

@@ -0,0 +1,39 @@
var React = require('react');
var KnotController = React.createClass({
getInitialState() {
return {
owner: false,
knots: []
};
},
bindKnots(owner, knots) {
this.setState({owner, knots});
},
render() {
var type = 'range';
var min = 0;
var max = this.state.knots.length;
var step = 1;
return (
<section className='knot-controls'><h2>knot values</h2>{
this.state.knots.map((value,position) => {
var props = {
type, min, max, step,
value,
onChange: e => {
var k = this.state.knots;
k[position] = e.target.value;
this.setState({ knots: k }, () => {
this.state.owner.redraw();
});
}
};
return <div key={'knot'+position}>{min}<input {...props}/>{max} (= {value})</div>;
})
}</section>
);
}
});
module.exports = KnotController;

View File

@@ -0,0 +1,53 @@
var React = require('react');
var WeightController = React.createClass({
getInitialState() {
return {
owner: false,
weights: [],
closed: false
};
},
bindWeights(owner, weights, closed) {
this.setState({owner, weights, closed});
},
render() {
var type = 'range';
var min = 0;
var max = this.state.weights.length;
var step = 1;
var overlap = this.state.closed;
var baselength = this.state.weights.length;
if (overlap !== false) {
baselength -= overlap;
}
return (
<section className='knot-controls'><h2>weight values</h2>{
this.state.weights.map((value,position) => {
if (overlap && position >= baselength) {
return null;
}
var props = {
type, min, max, step,
value,
onChange: e => {
var k = this.state.weights;
k[position] = e.target.value;
if (overlap && position < overlap) {
k[position+baselength] = e.target.value;
}
this.setState({ weights: k }, () => {
this.state.owner.redraw();
});
}
};
return <div key={'knot'+position}>{min}<input {...props}/>{max} (= {value})</div>;
})
}</section>
);
}
});
module.exports = WeightController;

View File

@@ -16,7 +16,7 @@ module.exports = {
this.line(n.x, n.y, p.x, p.y);
p = n;
this.stroke(0);
this.ellipse(p.x, p.y, 4);
this.circle(p.x, p.y, 4);
});
this.drawSplineData();
},

View File

@@ -0,0 +1,52 @@
module.exports = {
degree: 3,
activeDistance: 9,
setup() {
this.size(400, 400);
var TAU = Math.PI*2;
for (let i=0; i<TAU; i+=TAU/9) {
this.points.push({
x: this.width/2 + 100 * Math.cos(i),
y: this.height/2 + 100 * Math.sin(i)
});
}
this.knots = this.formKnots(this.points);
var m = Math.round(this.points.length/2)|0;
this.knots[m+0] = this.knots[m];
this.knots[m+1] = this.knots[m];
this.knots[m+2] = this.knots[m];
for (let i=m+3; i<this.knots.length; i++) {
this.knots[i] = this.knots[i-1] + 1;
}
if(this.props.controller) {
this.props.controller(this, this.knots);
}
this.draw();
},
draw() {
this.clear();
this.grid(25);
var p = this.points[0];
this.points.forEach(n => {
this.stroke(200);
this.line(n.x, n.y, p.x, p.y);
p = n;
this.stroke(0);
this.circle(p.x, p.y, 4);
});
this.drawSplineData();
},
drawSplineData() {
if (this.points.length <= this.degree) return;
var mapped = this.points.map(p => [p.x, p.y]);
this.drawCurve(mapped);
this.drawKnots(mapped);
}
};

View File

@@ -1,6 +1,8 @@
var React = require("react");
var BSplineGraphic = require("../../BSplineGraphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var KnotController = require("../../KnotController.jsx");
var WeightController = require("../../WeightController.jsx");
var BoundingBox = React.createClass({
getDefaultProps: function() {
@@ -9,6 +11,14 @@ var BoundingBox = React.createClass({
};
},
bindKnots: function(owner, knots, ref) {
this.refs[ref].bindKnots(owner, knots);
},
bindWeights: function(owner, weights, closed, ref) {
this.refs[ref].bindWeights(owner, weights, closed);
},
render: function() {
return (
<section>
@@ -70,11 +80,11 @@ var BoundingBox = React.createClass({
point on the curve for some value <code>t</code> in the interval [0,1] (where 0 is the start
of the curve, and 1 the end, just like for Bezier curves), by evaluting the following function:
</p>
<p>\[
Point(t) = \sum^n_{i=0} P_i \cdot N_{i,k}(t)
\]</p>
<p>
Which, honestly, doesn't tell us all that much. All we can see is that a point on a B-spline curve
is defined as "a mix of all the control points, weighted somehow", where the weighting is achieved
@@ -97,7 +107,7 @@ var BoundingBox = React.createClass({
<p>\[
N_{i,k}(t) = \left ( \frac{t-knot_i}{knot_{(i+k-1)} - knot_i}\right ) \cdot N_{i,k-1}(t) + \left ( \frac{knot_{(i+k)}-t}{knot_{(i+k)} - knot_{(i+1)}} \right ) \cdot N_{i+1,k-1}(t)
\]</p>
<p>
So this is where we see the interpolation: N(t) for an (i,k) pair (that is, for a step in the above summation,
on a specific knot interval) is a mix between N(t) for (i,k-1) and N(t) for (i+1,k-1), so we see that is a
@@ -105,10 +115,10 @@ var BoundingBox = React.createClass({
expect that this recursion has to stop at some point; obviously, it does, and specifically it does so for
the following <code>i</code>/<code>k</code> values:
</p>
<p>\[
N_{i,1}(t) = \left\{\begin{matrix}
1& \text{if } t \in [knot_i,knot_{i+1}) \\
1& \text{if } t \in [knot_i,knot_{i+1}) \\
0& \text{otherwise}
\end{matrix}\right.
\]</p>
@@ -120,13 +130,13 @@ var BoundingBox = React.createClass({
bounded by <code>knots[d]</code> and <code>knots[n]</code>, which are the start point and end point where curvature
is controlled by exactly <code>order</code> control points. For instance, for degree 3 (=order 4) and 7 control points,
with knot vector [1,2,3,4,5,6,7,8,9,10,11], we map <code>t</code> from [the interval 0,1] to the interval [4,8],
and then use that value in the functions above, instead.
and then use that value in the functions above, instead.
</p>
<h2>
Can we simplify that?
</h2>
<p>
We can, yes.
</p>
@@ -136,18 +146,18 @@ var BoundingBox = React.createClass({
to a mathematically pleasing solution: to compute a point P(t), we can compute this point by
evaluating <em>d(t)</em> on a curve section between knots <em>i</em> and <em>i+1</em>:
</p>
<p>\[
d^k_i(t) = \alpha_{i,k} \cdot d^{k-1}_i(t) + (1-\alpha_{i,k}) \cdot d^{k-1}_{i-1}(t)
\]</p>
This is another recursive function, with <em>k</em> values decreasing from the curve order to 1,
and the value <em>α</em> (alpha) defined by:
<p>\[
\alpha_{i,k} = \frac{t - knots[i]}{knots[i+1+n-k] - knots[i]}
\]</p>
<p>
That looks complicated, but it's not. Computing alpha is just a fraction involving known, plain numbers
and once we have our alpha value, computing (1-alpha) is literally just "computing one minus alpha".
@@ -156,25 +166,25 @@ var BoundingBox = React.createClass({
is also computationally cheap because each step only involves very simple maths. Of course as before
the recursion has to stop:
</p>
<p>\[
d^k_0(t) = 0, \ d^0_i(t) = N_{i,1}(t) =
d^k_0(t) = 0, \ d^0_i(t) = N_{i,1}(t) =
\left\{\begin{matrix}
1& \text{if } t \in [knot_i,knot_{i+1}) \\
1& \text{if } t \in [knot_i,knot_{i+1}) \\
0& \text{otherwise}
\end{matrix}\right.
\]</p>
<p>
So, we see two stopping conditions: either <code>i</code> becomes 0, in which case d() is zero,
or <code>k</code> becomes zero, in which case we get the same "either 1 or 0" that we saw in the N()
function above.</p>
<p>
Thanks to Cox and de Boor, we can compute points on a B-Spline pretty easily: we just need to compute
Thanks to Cox and de Boor, we can compute points on a B-Spline pretty easily: we just need to compute
a triangle of interconnected values. For instance, d() for i=3, k=3 yields the following triangle:
</p>
<p>\[\begin{array}{ccccccc}
d^3_3 && d^2_3 && d^1_3 && d^0_3 (= 0 \text{ or } 1) \\
&+^{α^3_3 \times }_{(1-{α^3_3}) \times }& &+^{α^2_3 \times }_{(1-{α^2_3}) \times }& &+^{α^1_3 \times }_{(1-{α^1_3}) \times }&\\
@@ -192,7 +202,7 @@ var BoundingBox = React.createClass({
That is, we compute d(3,3) as a mixture of d(2,3) and d(2,2): d(3,3) = a(3,3) x d(2,3) + (1-a(3,3)) x d(2,2)... and
we simply keep expanding our triangle until we reach the terminating function parameters. Done deal!
</p>
<p>
One thing we need to keep in mind is that we're working with a spline that is contrained by its control points,
so even though the \(d^k_i\) values are zero or one at the lowest level, they are really "zero or one, times their
@@ -203,7 +213,7 @@ var BoundingBox = React.createClass({
<p>
If we run this computation "down", starting at d(3,3), then without special code in place we would be computing quite a
few terms multiple times at each step. On the other hand, we can also start with that last "column", we can generate
few terms multiple times at each step. On the other hand, we can also start with that last "column", we can generate
the terminating d() values first, then compute the a() constants, perform our multiplcations, generate the previous
step's d() values, compute their a() constants, do the multiplications, etc. until we end up all the way back at the
top. If we run our computation this way, we don't need any explicit caching, we can just "recycle" the list of numbers
@@ -213,7 +223,7 @@ var BoundingBox = React.createClass({
<h2>
Running the computation
</h2>
<p>
Unlike the de Casteljau algorithm, where the <code>t</code> value stays the same at every iteration, for B-Splines that
is not the case, and so we end having to (for each point we evaluate) run a fairly involving bit of recursive computation.
@@ -221,12 +231,12 @@ var BoundingBox = React.createClass({
Tech</a> page, but an easier to read version is implemented
by <a href="https://github.com/thibauts/b-spline/blob/master/index.js#L59-L71">b-spline.js</a>, so we'll look at its code.
</p>
<p>
Given an input value <code>t</code>, we first map the input to a value from the domain [0,1] to the domain [knots[degree],
knots[knots.length - 1 - degree]. Then, we find the section number <code>s</code> that this mapped <code>t</code> value lies on:
</p>
<pre>
for(s=domain[0]; s < domain[1]; s++) {
if(knots[s] <= t && t <= knots[s+1]) break;
@@ -247,13 +257,25 @@ var BoundingBox = React.createClass({
let v[i] = alpha * v[i] + (1-alpha) * v[i-1]
}
}</pre>
<p>
(A nice bit of behaviour in this code is that we work the interpolation "backwards", starting at <code>i=s</code> at
each level of the interpolation, and we stop when <code>i = s - order + level</code>, so we always end up with a
value for <code>i</code> such that those <code>v[i-1]</code> don't have an array index that doesn't exist)
</p>
<h2>
Open vs. closed paths
</h2>
<p>
Much like poly-Beziers, B-Splines can be either open, running from the first point to the last point, or closed,
where the first and last point are <em>the same point</em>. However, because B-Splines are an interpolation of
curves, not just point, we can't simply make the first and last point the same, we need to link a few point point:
for an order <code>d</code> B-Spline, we need to make the last <code>d</code> point the same as the first <code>d</code> points.
And the easiest way to do this is to simply append <code>points.splice(0,d)</code> to <code>points</code>. Done!
</p>
<h2>
Manipulating the curve through the knot vector
</h2>
@@ -277,14 +299,17 @@ var BoundingBox = React.createClass({
<h3>Uniform B-Splines</h3>
<p>
The most straightforward type of B-spline is the uniform spline. In a uniform spline, the knots are distributed
The most straightforward type of B-spline is the uniform spline. In a uniform spline, the knots are distributed
uniformly over the entire curve interval. For instance, if we have a knot vector of length twelve, then a uniform
knot vector would be [0,1,2,3,...,9,10,11]. Or [4,5,6,...,13,14,15], which defines <em>the same intervals</em>,
or even [0,2,3,...,18,20,22], which also defines <em>the same intervals</em>, just scaled by a constant factor,
which becomes normalised during interpolation and so does not contribute to the curvature.
</p>
<p>SHOW A UNIFORM SPLINE HERE</p>
<div className="two-column">
<KnotController ref="uniform-spline" />
<BSplineGraphic sketch={require('./uniform-bspline')} controller={(owner, knots) => this.bindKnots(owner, knots, "uniform-spline")}/>
</div>
<p>
This is an important point: the intervals that the knot vector defines are <em>relative</em> intervals, so it
@@ -308,7 +333,10 @@ var BoundingBox = React.createClass({
collapsing <code>order</code> knots creates a situation where all continuity is lost and the curve "kinks".
</p>
<p>SHOW A CENTER-CUT SPLINE HERE</p>
<div className="two-column">
<KnotController ref="center-cut-bspline" />
<BSplineGraphic sketch={require('./center-cut-bspline')} controller={(owner, knots) => this.bindKnots(owner, knots, "center-cut-bspline")}/>
</div>
<h3>Open-Uniform B-splines</h3>
@@ -325,11 +353,14 @@ var BoundingBox = React.createClass({
"identical" knot vector [0,0,0,0,2,4,6,8,8,8,8], etc. Again, it is the relative differences that determine the curve shape.
</p>
<p>SHOW AN OPEN-UNIFORM SPLINE HERE</p>
<div className="two-column">
<KnotController ref="open-uniform-bspline" />
<BSplineGraphic sketch={require('./open-uniform-bspline')} controller={(owner, knots) => this.bindKnots(owner, knots, "open-uniform-bspline")}/>
</div>
<h3>Non-uniform B-splines</h3>
<p>This is essentialy the "free form" version of a B-spline, and also the least interesting to look at,
<p>This is essentialy the "free form" version of a B-spline, and also the least interesting to look at,
as without any specific reason to pick specific knot intervals, there is nothing particularly interesting
going on. There is on constraint to the knot vector, and that is that any value <code>knots[k+1]</code>
should be equal to, or greater than <code>knots[k]</code>.</p>
@@ -343,7 +374,16 @@ var BoundingBox = React.createClass({
point carries, the close to that point the spline curve will lie, a bit like turning up the gravity
of a control point.</p>
<p>SHOW A WEIGHT-MANIPULABLE RATIONAL B-SPLINE HERE</p>
<div className="two-column">
{
// <KnotController ref="rational-uniform-bspline" />
}
<WeightController ref="rational-uniform-bspline-weights" />
<BSplineGraphic scrolling={true} sketch={require('./rational-uniform-bspline')} controller={(owner, knots, weights, closed) => {
// this.bindKnots(owner, knots, "rational-uniform-bspline");
this.bindWeights(owner, weights, closed, "rational-uniform-bspline-weights");
}} />
</div>
<p>Of course this brings us to the final topic that any text on B-splines must touch on before calling it
a day: the NURBS, or Non-Uniform Rational B-Spline (NUBRS is not a plural, the capital S actually just stands
@@ -352,13 +392,11 @@ var BoundingBox = React.createClass({
as well as in arbitrary-precision 2D design due to the level of control a NURBS curve offers designers.
</p>
<p>While a true non-uniform rational B-spline would be hard to work with, when we talk about NURBS we
<p>While a true non-uniform rational B-spline would be hard to work with, when we talk about NURBS we
typically mean the Open-Uniform Rational B-Spline, or OURBS, but that doesn't roll off the tongue nearly
as nicely, and so remember that when people talk about NURBS, they typically mean open-uniform, which
has the useful property of starting the curve at the first control point, and ending it at the last.</p>
<p>SHOW A NURBS HERE</p>
<h2>Extending our implementation to cover rational splines</h2>
<p>
@@ -387,7 +425,7 @@ var BoundingBox = React.createClass({
</p>
<p>
Based on our previous example, we take the final 3D point <code>(x', y', w')</code>, which we then
Based on our previous example, we take the final 3D point <code>(x', y', w')</code>, which we then
turn back into a 2D point by computing <code>(x'/w', y'/w')</code>. And that's it, we're done!
</p>

View File

@@ -24,8 +24,9 @@ module.exports = {
var nt = n+k+1;
var w2 = this.width/2;
var h1 = this.height;
var step = 0.1;
var ti = [0,1,2,3,4,5,6];
var step = 0.1, t = ti[0];
var t = ti[0];
var N = [[],[],[],[],[],[],[],[]];
var i1 = 0;
@@ -39,7 +40,7 @@ module.exports = {
}
N[i][l] = 1;
// basis functions calculation
for (var m = 2; m <= k; m++) {
for (var m = 2; m <= k; m++) {
var jb = i-m+1;
if (jb < 0) {
jb = 0;
@@ -52,10 +53,20 @@ module.exports = {
t += step;
}
var stw = this.width/6;
var colors = [
'#C00',
'#CC0',
'#0C0',
'#0CC',
'#00C',
'#C0C'
];
var stw = this.width/8;
for (let j = 0; j < n1; j++) {
t = ti[0];
let to = t;
this.stroke(colors[j]);
for (let l = 1; l < w2; l++) {
t += step;
let t1 = t;
@@ -68,5 +79,11 @@ module.exports = {
to = t1;
}
}
this.stroke(0);
this.fill(0);
for(let j=0; j<n+k+1; j++) {
this.circle(pad + j*stw, h1 - pad, 3);
}
}
};

View File

@@ -0,0 +1,45 @@
module.exports = {
degree: 3,
activeDistance: 9,
setup() {
this.size(400, 400);
var TAU = Math.PI*2;
for (let i=0; i<TAU; i+=TAU/10) {
this.points.push({
x: this.width/2 + 100 * Math.cos(i),
y: this.height/2 + 100 * Math.sin(i)
});
}
this.knots = this.formKnots(this.points, true);
if(this.props.controller) {
this.props.controller(this, this.knots);
}
this.draw();
},
draw() {
this.clear();
this.grid(25);
var p = this.points[0];
this.points.forEach(n => {
this.stroke(200);
this.line(n.x, n.y, p.x, p.y);
p = n;
this.stroke(0);
this.circle(p.x, p.y, 4);
});
this.drawSplineData();
},
drawSplineData() {
if (this.points.length <= this.degree) return;
var mapped = this.points.map(p => [p.x, p.y]);
this.drawCurve(mapped);
this.drawKnots(mapped);
}
};

View File

@@ -0,0 +1,42 @@
module.exports = {
degree: 3,
activeDistance: 9,
weights: [],
setup() {
this.size(400, 400);
var TAU = Math.PI*2;
for (let i=0; i<TAU; i+=TAU/10) {
this.points.push({
x: this.width/2 + 100 * Math.cos(i),
y: this.height/2 + 100 * Math.sin(i)
});
}
this.knots = this.formKnots(this.points, true);
this.weights = this.formWeights(this.points);
this.draw();
},
draw() {
this.clear();
this.grid(25);
var p = this.points[0];
this.points.forEach(n => {
this.stroke(200);
this.line(n.x, n.y, p.x, p.y);
p = n;
this.stroke(0);
this.circle(p.x, p.y, 4);
});
this.drawSplineData();
},
drawSplineData() {
if (this.points.length <= this.degree) return;
var mapped = this.points.map(p => [p.x, p.y]);
this.drawCurve(mapped);
this.drawKnots(mapped);
}
};

View File

@@ -0,0 +1,50 @@
module.exports = {
degree: 3,
activeDistance: 9,
weights: [],
setup() {
this.size(400, 400);
var TAU = Math.PI*2;
var r = this.width/3;
for (let i=0; i<6; i++) {
this.points.push({
x: this.width/2 + r * Math.cos(i/6 * TAU),
y: this.height/2 + r * Math.sin(i/6 * TAU)
});
}
this.points = this.points.concat(this.points.slice(0,3));
this.closed = this.degree;
this.knots = this.formKnots(this.points);
this.weights = this.formWeights(this.points);
if(this.props.controller) {
this.props.controller(this, this.knots, this.weights, this.closed);
}
this.draw();
},
draw() {
this.clear();
this.grid(25);
var p = this.points[0];
this.points.forEach(n => {
this.stroke(200);
this.line(n.x, n.y, p.x, p.y);
p = n;
this.stroke(0);
this.circle(p.x, p.y, 4);
});
this.drawSplineData();
},
drawSplineData() {
if (this.points.length <= this.degree) return;
var mapped = this.points.map(p => [p.x, p.y]);
this.drawCurve(mapped);
this.drawKnots(mapped);
}
};

View File

@@ -0,0 +1,43 @@
module.exports = {
degree: 3,
activeDistance: 9,
setup() {
this.size(400, 400);
var TAU = Math.PI*2;
for (let i=0; i<TAU; i+=TAU/10) {
this.points.push({
x: this.width/2 + 100 * Math.cos(i),
y: this.height/2 + 100 * Math.sin(i)
});
}
this.knots = this.formKnots(this.points);
if(this.props.controller) {
this.props.controller(this, this.knots);
}
this.draw();
},
draw() {
this.clear();
this.grid(25);
var p = this.points[0];
this.points.forEach(n => {
this.stroke(200);
this.line(n.x, n.y, p.x, p.y);
p = n;
this.stroke(0);
this.circle(p.x, p.y, 4);
});
this.drawSplineData();
},
drawSplineData() {
if (this.points.length <= this.degree) return;
var mapped = this.points.map(p => [p.x, p.y]);
this.drawCurve(mapped);
this.drawKnots(mapped);
}
};