1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-17 22:11:38 +02:00

control section

This commit is contained in:
Pomax
2015-12-29 16:59:12 -08:00
parent fd0d07a4be
commit 54fb8b2cef
17 changed files with 1403 additions and 566 deletions

View File

@@ -9,12 +9,45 @@ var Article = React.createClass({
};
},
setSectionId: function(name, entry) {
ReactDOM.findDOMNode(this.refs[name]).setAttribute("id", name);
},
componentDidMount: function() {
// Not sure why this doesn't just work by passing a props.id
this.sectionMap(this.setSectionId);
},
sectionMap: function(mapping) {
return this.getSectionNames().map(mapping);
},
getSectionNames: function() {
return Object.keys(this.state.sections);
},
generateSection: function(name, entry) {
var Type = this.state.sections[name];
return <Type key={name} ref={name} name={name} number={entry}/>;
},
generateNavItem: function(name, entry) {
var Type = this.state.sections[name];
return <li data-number={entry}><a href={'#' + name}>{Type.title || name}</a></li>;
},
render: function() {
var sections = Object.keys(this.state.sections).map((name, entry) => {
var Type = this.state.sections[name];
return <Type key={name} number={1+entry} />;
});
return <div>{ sections }</div>;
var sections = this.sectionMap(this.generateSection);
return (<div>
<div ref="navigation">
<navigation>
<ul className="navigation">
{ this.sectionMap(this.generateNavItem) }
</ul>
</navigation>
</div>
<div ref="sections">{ sections }</div>
</div>);
}
});

View File

@@ -26,7 +26,7 @@ var Graphic = React.createClass({
render: function() {
var href = "data:text/plain," + this.props.code;
return (
<figure>
<figure className={this.props.inline ? "inline": false}>
<canvas ref="canvas"
onMouseDown={this.mouseDown}
onMouseMove={this.mouseMove}
@@ -45,8 +45,14 @@ var Graphic = React.createClass({
this.cvs = cvs;
var ctx = cvs.getContext("2d");
this.ctx = ctx;
this.props.setup(this);
this.props.draw(this,this.curve);
if (this.props.setup) {
this.props.setup(this);
}
if (this.props.draw) {
this.props.draw(this,this.curve);
}
},
mouseDown: function(evt) {
@@ -81,7 +87,10 @@ var Graphic = React.createClass({
this.mp.x = this.cx + this.ox;
this.mp.y = this.cy + this.oy;
this.curve.update();
this.props.draw(this, this.curve);
if (this.props.draw) {
this.props.draw(this, this.curve);
}
},
mouseUp: function(evt) {
@@ -146,6 +155,10 @@ var Graphic = React.createClass({
this.ctx.strokeStyle = c;
},
getColor: function() {
return this.ctx.strokeStyle || "black";
},
setWeight: function(c) {
this.ctx.lineWidth = c;
},
@@ -155,8 +168,8 @@ var Graphic = React.createClass({
},
setRandomColor: function() {
var r = ((255*Math.random())|0),
g = ((255*Math.random())|0),
var r = ((205*Math.random())|0),
g = ((155*Math.random())|0),
b = ((255*Math.random())|0);
this.ctx.strokeStyle = "rgb("+r+","+g+","+b+")";
},
@@ -173,6 +186,10 @@ var Graphic = React.createClass({
this.ctx.fillStyle = c;
},
getFill: function() {
return this.ctx.fillStyle || "transparent";
},
noFill: function() {
this.ctx.fillStyle = "transparent";
},
@@ -194,10 +211,23 @@ var Graphic = React.createClass({
var pts = curve.points;
this.setFill("black");
pts.forEach(p => {
this.drawText(`(${p.x},${p.y})`, {x:p.x + 5, y:p.y + 10});
this.text(`(${p.x},${p.y})`, {x:p.x + 5, y:p.y + 10});
});
},
drawFunction: function(generator, offset) {
var p0 = generator(0),
plast = generator(1),
step = generator.step || 0.01,
p, t;
for (t=step; t<1.0; t+=step) {
p = generator(t);
this.drawLine(p0, p, offset);
p0 = p;
}
this.drawLine(p, plast, offset);
},
drawCurve: function(curve, offset) {
offset = offset || { x:0, y:0 };
var ox = offset.x + this.offset.x;
@@ -315,7 +345,7 @@ var Graphic = React.createClass({
this.ctx.stroke();
},
drawText: function(text, offset) {
text: function(text, offset) {
offset = offset || { x:0, y:0 };
if (this.offset) {
offset.x += this.offset.x;

View File

@@ -1,34 +1,31 @@
var React = require("react");
var ReactDOM = require("react-dom");
var noop = require("../lib/noop");
var MathJax = (typeof window !== "undefined" ? window.MathJax : false);
var noop = function() {};
// fallback will simply do nothing when typesetting.
if(!MathJax){MathJax={Hub:{Queue:noop}};}
var LaTeX = React.createClass({
mixins: [
require("react-component-visibility")
],
componentDidMount: function() {
this.setComponentVisbilityRateLimit(200);
getInitialState() {
var data = this.props.children;
if (!data.forEach) data = [data];
return { latex: data.join('') };
},
componentVisibilityChanged: function() {
var visible = this.state.visible;
if (visible) {
MathJax.Hub.Queue(["Typeset", MathJax.Hub, this.refs.latex, false]);
this.componentVisibilityChanged = noop;
}
componentDidMount: function() {
MathJax.Hub.Queue(["Typeset", MathJax.Hub, this.refs.latex, false]);
MathJax.Hub.Queue(this.bindHTML);
},
bindHTML: function() {
this.setState({ html: this.refs.latex.innerHTML });
},
render: function() {
var data = this.props.children;
if (!data.forEach) data = [data];
return <p ref="latex" dangerouslySetInnerHTML={{__html: data.join('') }} />;
var content = this.state.html ? this.state.html : this.state.latex;
return <p ref="latex" dangerouslySetInnerHTML={{__html: content }} />;
}
});

View File

@@ -3,7 +3,7 @@ var ReactDOM = require("react-dom");
var SectionHeader = React.createClass({
render: function() {
return <h2 data-num={this.props.number}>{this.props.children}</h2>;
return <h2 data-num={this.props.number}><a href={'#' + this.props.name}>{this.props.children}</a></h2>;
}
});

View File

@@ -0,0 +1,261 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var LaTeX = require("../../LaTeX.jsx");
var Control = React.createClass({
statics: {
title: "Controlling Bézier curvatures"
},
drawCubic: function(api) {
var curve = api.getDefaultCubic();
api.setCurve(curve);
},
drawCurve: function(api, curve) {
api.reset();
api.drawSkeleton(curve);
api.drawCurve(curve);
},
drawGraphBasics: function(api, dim, pad, fwh) {
api.drawLine({x:pad, y:pad}, {x:dim-pad, y:pad});
api.drawLine({x:pad, y:pad}, {x:pad, y:dim-pad});
api.setFill("black");
api.text("t →", {x:dim/2, y: 15});
api.text("0", {x:pad, y: 15});
api.text("1", {x:dim-pad, y: 15});
api.text("S", {x:5, y: dim/2-pad});
api.text("↓", {x:5, y: dim/2});
api.text("0%", {x:4, y: pad+5});
api.text("100%", {x:2, y: dim-pad+10});
},
drawFunction: function(api, label, where, generator) {
api.setRandomColor();
api.drawFunction(generator);
api.setFill(api.getColor());
if (!!label) api.text(label, where);
},
drawQuadraticLerp: function(api) {
var dim = api.getPanelWidth(),
pad = 20,
fwh = dim - pad*2;
this.drawGraphBasics(api, dim, pad, fwh);
this.drawFunction(api, "first term", {x: pad*2, y: fwh}, function(t) {
return {
x: pad + t * fwh,
y: pad + fwh * (1-t) * (1-t)
};
});
this.drawFunction(api, "second term", {x: dim/2 - 1.5*pad, y: dim/2 + pad}, function(t) {
return {
x: pad + t * fwh,
y: pad + fwh * 2 * (1-t) * (t)
};
});
this.drawFunction(api, "third term", {x: fwh - pad*2.5, y: fwh}, function(t) {
return {
x: pad + t * fwh,
y: pad + fwh * (t) * (t)
};
});
},
drawCubicLerp: function(api) {
var dim = api.getPanelWidth(),
pad = 20,
fwh = dim - pad*2;
this.drawGraphBasics(api, dim, pad, fwh);
this.drawFunction(api, "first term", {x: pad*2, y: fwh}, function(t) {
return {
x: pad + t * fwh,
y: pad + fwh * (1-t) * (1-t) * (1-t)
};
});
this.drawFunction(api, "second term", {x: dim/2 - 4*pad, y: dim/2 }, function(t) {
return {
x: pad + t * fwh,
y: pad + fwh * 3 * (1-t) * (1-t) * (t)
};
});
this.drawFunction(api, "third term", {x: dim/2 + 2*pad, y: dim/2}, function(t) {
return {
x: pad + t * fwh,
y: pad + fwh * 3 * (1-t) * (t) * (t)
};
});
this.drawFunction(api, "fourth term", {x: fwh - pad*2.5, y: fwh}, function(t) {
return {
x: pad + t * fwh,
y: pad + fwh * (t) * (t) * (t)
};
});
},
draw15thLerp: function(api) {
var dim = api.getPanelWidth(),
pad = 20,
fwh = dim - pad*2;
this.drawGraphBasics(api, dim, pad, fwh);
var factor = [1,15,105,455,1365,3003,5005,6435,6435,5005,3003,1365,455,105,15,1]
for(var n=0; n<=15; n++) {
var label = false, position = false;
if (n===0) {
label = "first term";
position = {x: pad + 5, y: fwh};
}
if (n===15) {
label = "last term";
position = {x: dim - 3.5*pad, y: fwh};
}
this.drawFunction(api, label, position, function(t) {
return {
x: pad + t * fwh,
y: pad + fwh * factor[n] * Math.pow(1-t, 15-n) * Math.pow(t, n)
};
});
}
},
render: function() {
return (
<section>
<SectionHeader {...this.props}>{ Control.title }</SectionHeader>
<p>Bézier curves are (like all "splines") interpolation functions, meaning they take a set of
points, and generate values somewhere "between" those points. (One of the consequences of this
is that you'll never be able to generate a point that lies outside the outline for the control
points, commonly called the "hull" for the curve. Useful information!). In fact, we can visualize
how each point contributes to the value generated by the function, so we can see which points are
important, where, in the curve.</p>
<p>The following graphs show the interpolation functions for quadratic and cubic curves, with "S"
being the strength of a point's contribution to the total sum of the Bézier function. Click or
click-drag to see the interpolation percentages for each curve-defining point at a
specific <i>t</i> value.</p>
<div className="figure">
<Graphic inline={true} preset="simple" title="Quadratic interpolations" draw={this.drawQuadraticLerp}/>
<Graphic inline={true} preset="simple" title="Cubic interpolations" draw={this.drawCubicLerp}/>
<Graphic inline={true} preset="simple" title="15th order interpolations" draw={this.draw15thLerp}/>
</div>
<p>Also shown is the interpolation function for a 15<sup>th</sup> order Bézier function. As you can see,
the start and end point contribute considerably more to the curve's shape than any other point
in the control point set.</p>
<p>If we want to change the curve, we need to change the weights of each point, effectively changing
the interpolations. The way to do this is about as straight forward as possible: just multiply each
point with a value that changes its strength. These values are conventionally called "Weights", and
we can add them to our original Bézier function:</p>
<LaTeX>\[
Bézier(n,t) = \sum_{i=0}^{n}
\underset{binomial\ term}{\underbrace{\binom{n}{i}}}
\cdot\
\underset{polynomial\ term}{\underbrace{(1-t)^{n-i} \cdot t^{i}}}
\cdot\
\underset{weight}{\underbrace{w_i}}
\]</LaTeX>
<p>That looks complicated, but as it so happens, the "weights" are actually just the coordinate values
we want our curve to have: for an <i>n<sup>th</sup></i> order curve, w<sub>0</sub> is our start coordinate,
w<sub>n</sub> is our last coordinate, and everything in between is a controlling coordinate. Say we want
a cubic curve that starts at (120,160), is controlled by (35,200) and (220,260) and ends at (220,40),
we use this Bézier curve:</p>
<LaTeX>\[
\left \{ \begin{matrix}
x = BLUE[120] \cdot (1-t)^3 + BLUE[35] \cdot 3 \cdot (1-t)^2 \cdot t + BLUE[220] \cdot 3 \cdot (1-t) \cdot t^2 + BLUE[220] \cdot t^3 \\
y = BLUE[160] \cdot (1-t)^3 + BLUE[200] \cdot 3 \cdot (1-t)^2 \cdot t + BLUE[260] \cdot 3 \cdot (1-t) \cdot t^2 + BLUE[40] \cdot t^3
\end{matrix} \right. \]</LaTeX>
<p>Which gives us the curve we saw at the top of the article:</p>
<Graphic preset="simple" title="Our cubic Bézier curve" setup={this.drawCubic} draw={this.drawCurve}/>
<p>What else can we do with Bézier curves? Quite a lot, actually. The rest of this article covers
a multitude of possible operations and algorithms that we can apply, and the tasks they achieve.</p>
<div className="howtocode">
<h3>How to implement the weighted basis function</h3>
<p>Given that we already know how to implement basis function, adding in the control points
is remarkably easy:</p>
<pre>function Bezier(n,t,w[]):
sum = 0
for(k=0; k&lt;n; k++):
sum += w[k] * binomial(n,k) * (1-t)^(n-k) * t^(k)
return sum</pre>
<p>And for the extremely optimized versions:</p>
<pre>function Bezier(2,t,w[]):
t2 = t * t
mt = 1-t
mt2 = mt * mt
return w[0]*mt2 + w[1]*2*mt*t + w[2]*t2
function Bezier(3,t,w[]):
t2 = t * t
t3 = t2 * t
mt = 1-t
mt2 = mt * mt
mt3 = mt2 * mt
return w[0]*mt3 + 3*w[1]*mt2*t + 3*w[2]*mt*t2 + w[3]*t3</pre>
<p>And now we know how to program the weighted basis function.</p>
</div>
</section>
);
}
});
module.exports = Control;
/**
<textarea class="sketch-code" data-sketch-preset="ratios" data-sketch-title="Quadratic interpolations">
int order = 3;
</textarea>
<textarea class="sketch-code" data-sketch-preset="ratios" data-sketch-title="Cubic interpolations">
int order = 4;
</textarea>
<textarea class="sketch-code" data-sketch-preset="ratios" data-sketch-title="15th order interpolations">
int order = 15;
</textarea>
**/
/**
void setupCurve() {
setupDefaultCubic();
}
void drawCurve(BezierCurve curve) {
curve.draw();
}</Graphic>
**/

View File

@@ -25,7 +25,7 @@ module.exports = {
sketch.drawPoint(p, offset);
var modulo = t % 1;
if(modulo<0.05 || modulo> 0.95) {
sketch.drawText("t = " + Math.round(t), {
sketch.text("t = " + Math.round(t), {
x: offset.x + 1.25 * w4 * Math.cos(t) - 10,
y: offset.y + 1.25 * h4 * Math.sin(t) + 5
});

View File

@@ -4,6 +4,10 @@ var SectionHeader = require("../../SectionHeader.jsx");
var LaTeX = require("../../LaTeX.jsx");
var Explanation = React.createClass({
statics: {
title: "The basics of Bézier curves"
},
circle: require("./circle"),
componentWillMount: function() {
@@ -14,7 +18,7 @@ var Explanation = React.createClass({
render: function() {
return (
<section>
<SectionHeader {...this.props}>The basics of Bézier curves?</SectionHeader>
<SectionHeader {...this.props}>{ Explanation.title }</SectionHeader>
<p>Bézier curves are a form of "parametric" function. Mathematically speaking, parametric
functions are cheats: a "function" is actually a well defined term representing a mapping

View File

@@ -6,12 +6,12 @@ module.exports = {
preface: require("./preface"),
introduction: require("./introduction"),
whatis: require("./whatis"),
explanation: require("./explanation")
explanation: require("./explanation"),
control: require("./control")
};
/*
control: require("./control.jsx"),
matrix: require("./matrix.jsx"),
decasteljau: require("./decasteljau.jsx"),

View File

@@ -2,8 +2,11 @@ var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Introduction = React.createClass({
statics: {
title: "A lightning introduction"
},
drawQuadratic: function(api) {
var curve = api.getDefaultQuadratic();
api.setCurve(curve);
@@ -23,7 +26,7 @@ var Introduction = React.createClass({
render: function() {
return (
<section>
<SectionHeader {...this.props}>A lightning introduction</SectionHeader>
<SectionHeader {...this.props}>{Introduction.title}</SectionHeader>
<p>Let's start with the good stuff: when we're talking about Bézier curves, we're talking about the
things that you can see in the following graphics. They run from some start point to some end point,
@@ -31,9 +34,10 @@ var Introduction = React.createClass({
graphics on this page are interactive, go manipulate those curves a bit: click-drag the points,
and see how their shape changes based on what you do.</p>
<Graphic title="Quadratic Bézier curves" setup={ this.drawQuadratic } draw={ this.drawCurve }/>
<Graphic title="Cubic Bézier curves" setup={ this.drawCubic } draw={ this.drawCurve }/>
<div className="figure">
<Graphic inline={true} title="Quadratic Bézier curves" setup={ this.drawQuadratic } draw={ this.drawCurve }/>
<Graphic inline={true} title="Cubic Bézier curves" setup={ this.drawCubic } draw={ this.drawCurve }/>
</div>
<p>These curves are used a lot in computer aided design and computer aided manufacturing (CAD/CAM)
applications, as well as in graphic design programs like Adobe Illustrator and Photoshop, Inkscape,

View File

@@ -1,10 +1,14 @@
var React = require("react");
var Preface = React.createClass({
statics: {
title: "Preface"
},
render: function() {
return (
<section>
<h2>{Preface.title}</h2>
<p>In order to draw things in 2D, we usually rely on lines, which typically get classified
into two categories: straight lines, and curves. The first of these are as easy to draw as they
@@ -30,8 +34,8 @@ var Preface = React.createClass({
Citroën, coming up with a really elegant way of figuring out how to draw them. However, de
Casteljau did not publish his work, making the question "who was first" hard to answer in
any absolute sense. Or is it? Bézier curves are, at their core, "Bernstein polynomials", a family
of mathematical functions investigated by
<a href="https://en.wikipedia.org/wiki/Sergei_Natanovich_Bernstein">Sergei Natanovich Bernstein</a>,
of mathematical functions investigated
by <a href="https://en.wikipedia.org/wiki/Sergei_Natanovich_Bernstein">Sergei Natanovich Bernstein</a>,
with publications on them at least as far back as 1912. Anyway, that's mostly trivia, what
you are more likely to care about is that these curves are handy: you can link up multiple
Bézier curves so that the combination looks like a single curve. If you've ever drawn Photoshop

View File

@@ -5,6 +5,10 @@ var LaTeX = require("../../LaTeX.jsx");
var Whatis = React.createClass({
statics: {
title: "So what makes a Bézier Curve?"
},
interpolation: require("./interpolation"),
componentWillMount: function() {
@@ -15,46 +19,50 @@ var Whatis = React.createClass({
render: function() {
return (
<section>
<SectionHeader {...this.props}>What is a Bézier Curve?</SectionHeader>
<SectionHeader {...this.props}>{Whatis.title}</SectionHeader>
<p>Playing with the points for curves may have given you a feel for how Bézier curves behaves, but
what <em>are</em> Bézier curves, really?</p>
<p>There are two ways to explain what a Bézier curve is, and they turn out to be the entirely equivalent,
but one of them uses complicated maths, and the other uses really simple maths. So... let's start
with the simple explanation:</p>
what <em>are</em> Bézier curves, really? There are two ways to explain what a Bézier curve is, and
they turn out to be the entirely equivalent, but one of them uses complicated maths, and the other
uses really simple maths. So... let's start with the simple explanation:</p>
<p>Bezier curves are the result of <a href="https://en.wikipedia.org/wiki/Linear_interpolation">linear
interpolations</a>. That sounds complicated but you've been doing linear interpolation since you were
very young: any time you had to point at something between two other things, you've been applying
linear interpolation. It's simply "picking a point between two, points". If we know the distance
between those two points, and we want a new point that is, say, 20% the distance away from
the first point (and thus 80% the distance away from the second point) then we can compute that
really easily:</p>
linear interpolation. It's simply "picking a point between two, points".</p>
<p>If we know the distance between those two points, and we want a new point that is, say, 20% the
distance away from the first point (and thus 80% the distance away from the second point) then we
can compute that really easily:</p>
<LaTeX>\[
p_1 = some\ point, \\
p_2 = some\ other\ point, \\
distance = (p_2 - p_1), \\
ratio = \frac{percentage}{100}, \\
new\ point = p_1 + distance \cdot ratio
Given \left (
\begin{align}
p_1 &= some\ point \\
p_2 &= some\ other\ point \\
distance &= (p_2 - p_1) \\
ratio &= \frac{percentage}{100} \\
\end{align}
\right ),\ our\ new\ point = p_1 + distance \cdot ratio
\]</LaTeX>
<p>So let's look at that in action: the following graphic is interactive in that you can use your
'+' and '-' keys to increase or decrease the interpolation distance, to see what happens. We start
with three points, which gives us two lines. Linear interpolation over those lines gives use two
points, between which we can again perform linear interpolation, yielding a single point. And that
point, and all points we can form in this way for all distances taken together, form our Bézier curve:</p>
point and all points we can form in this way for all distances taken together form our Bézier curve:</p>
<Graphic title="Linear Interpolation leading to Bézier curves" setup={this.setup} draw={this.draw}/>
<p>And that brings us to the complicated maths: calculus. While it doesn't look like that's what we've just done,
we actually just drew a quadratic curve, in steps, rather than in a single go. One of the fascinating parts
about Bézier curves is that they can both be described in terms of polynomial functions, as well as in terms
of very simple interpolations of interpolations of [...]. That it turn means we can look at what these curves
can do based on both "real maths" (by examining the functions, their derivatives, and all that stuff), as well
as by looking at the "mechanical" composition (which tells us that a curve will never extend beyond the points
we used to construct it, for instance)</p>
<p>And that brings us to the complicated maths: calculus.</p>
<p>While it doesn't look like that's what we've just done, we actually just drew a quadratic curve, in steps,
rather than in a single go. One of the fascinating parts about Bézier curves is that they can both be described
in terms of polynomial functions, as well as in terms of very simple interpolations of interpolations of [...].
That it turn means we can look at what these curves can do based on both "real maths" (by examining the functions,
their derivatives, and all that stuff), as well as by looking at the "mechanical" composition (which tells us
that a curve will never extend beyond the points we used to construct it, for instance)</p>
<p>So let's start looking at Bézier curves a bit more in depth. Their mathematical expressions, the properties we
can derive from those, and the various things we can do to, and with, Bézier curves.</p>

View File

@@ -62,7 +62,7 @@ module.exports = {
sketch.setColor("black");
sketch.setFill("black");
sketch.drawSkeleton(curve);
sketch.drawCurve(curve);
//sketch.drawCurve(curve);
// draw 20% off-start points and struts
sketch.setWeight(2);
@@ -85,7 +85,7 @@ module.exports = {
sketch.drawCircle(p1e3,3);
sketch.drawCircle(p2e3,3);
sketch.drawText("First linear interpolation at 20% / 40% / 60%", {x:5, y:15});
sketch.text("First linear interpolation at 20% / 40% / 60%", {x:5, y:15});
// next panel
sketch.setColor("black");
@@ -94,7 +94,7 @@ module.exports = {
sketch.drawLine({x:0, y:0}, {x:0, y:this.dim});
sketch.drawSkeleton(curve);
sketch.drawCurve(curve);
//sketch.drawCurve(curve);
sketch.setColor("rgb(100,100,200)");
sketch.drawLine(p1e, p2e);
@@ -124,7 +124,7 @@ module.exports = {
sketch.drawLine(p1e3, m3);
sketch.drawCircle(m3,3);
sketch.drawText("Second interpolation at 20% / 40% / 60%", {x:5, y:15});
sketch.text("Second interpolation at 20% / 40% / 60%", {x:5, y:15});
// next panel
sketch.setColor("black");
@@ -133,11 +133,18 @@ module.exports = {
sketch.drawLine({x:0, y:0}, {x:0, y:this.dim});
sketch.drawSkeleton(curve);
sketch.drawCurve(curve);
sketch.setColor("lightgrey");
for(var t=1,d=20,v,tvp; t<d; t++) {
v = t/d;
tvp = curve.get(v);
sketch.drawCircle(tvp,2);
}
sketch.setColor("black");
sketch.drawCircle(m,3);
sketch.drawCircle(m2,3);
sketch.drawCircle(m3,3);
sketch.drawText("Curve points for t = 0.2, t = 0.4, t = 0.6", {x:5, y:15});
sketch.text("Curve points for t = 0.2, t = 0.4, t = 0.6", {x:5, y:15});
}
};