xxxxxxxxxx
385
var bezierCurve;
var slider;
var sliderMax = 100;
var checkBoxAutoPlay;
let checkedBoxAutoPlay = false;
let addToSlider = 1;
var checkBoxShowConstructLines;
var checkBoxShowControlPolygonLines;
var chhckBoxShowCurveTrace;
var granularity, button, button1;
function setup() {
bezierCurve = new BezierCurve();
frameRate(30); //change this for the slider autoplay velocity
var myCanvas = createCanvas(windowWidth / 1.6, windowHeight / 1.6);
slider = createSlider(0, sliderMax, 1);
slider.position(10, 10);
slider.style("width", "200px");
slider.value(0);
checkBoxAutoPlay = createCheckbox("auto play", checkedBoxAutoPlay);
checkBoxAutoPlay.changed(myEventCheckBoxAutoPlay);
checkBoxShowConstructLines = createCheckbox("show construct lines", true);
checkBoxShowConstructLines.changed(myEventCheckBoxShowConstructLines);
checkBoxShowControlPolygonLines = createCheckbox(
"show control polygon",
true
);
checkBoxShowControlPolygonLines.changed(
myEventCheckBoxShowControlPolygonLines
);
checkBoxShowCurveTrace = createCheckbox("show curve", true);
checkBoxShowCurveTrace.changed(myEventCheckBoxShowCurveTrace);
granularity = createInput("10-1000");
granularity.position(400, 10);
button = createButton("granularity");
button.position(granularity.x + granularity.width, 10);
button.mousePressed(myEventChangeGranularity);
button1 = createButton("degree elevation");
button1.position(10, 50);
button1.mousePressed(myEventDegreeElevation);
}
function myEventChangeGranularity() {
bezierCurve.changeGranularity(granularity.value());
}
function draw() {
//clear();
//background(220, 10);
background(255);
if (checkedBoxAutoPlay) {
if (slider.value() == sliderMax) addToSlider = -1;
if (slider.value() == 0 && addToSlider < 0) addToSlider = 1;
slider.value(slider.value() + addToSlider);
}
bezierCurve.render();
}
class ConstructLine {
constructor(p1 = null, p2 = null) {
this.p1 = p1;
this.p2 = p2;
}
render() {
//stroke(126);
strokeWeight(1.5);
line(this.p1.x, this.p1.y, this.p2.x, this.p2.y);
}
}
class ConstructPoint {
constructor(x = null, y = null) {
this.x = x;
this.y = y;
}
render() {
stroke(0);
strokeWeight(2);
ellipse(this.x, this.y, 2, 2);
}
}
function mapSpace(x, in_min, in_max, out_min, out_max) {
return ((x - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min;
}
function linspace(startValue, stopValue, cardinality) {
var arr = [];
var step = (stopValue - startValue) / (cardinality - 1);
for (var i = 0; i < cardinality; i++) {
arr.push(startValue + step * i);
}
return arr;
}
function myEventCheckBoxAutoPlay() {
checkedBoxAutoPlay = !checkedBoxAutoPlay;
}
class BezierCurve {
constructor() {
//TODO add the possibility to create a curve passing arguments
this.controlPoints = [];
this.draggedControlPointIndex = -1; // by convention = -1 if we are not dragging any point
this.controlPointsX = [];
this.controlPointsY = [];
this.granularity = 1000;
this.t = linspace(0, 1, this.granularity);
this.constructLines = [];
this.constructPoints = [];
this.controlPolygonLines = [];
this.checkedShowControlPolygon = true;
this.checkedShowConstructLines = true;
this.checkedShowCurveTrace = true;
}
degreeElevation() {
let lastX, lastY, n;
n = this.controlPoints.length - 1;
lastX = this.controlPoints[n].x;
lastY = this.controlPoints[n].y;
//cant do in place because changing b_i in one iteration then the next cant retrive true b_i value
//using duplicate copys of coordinates (required extra loop) to do it easily
for (let i = 1; i <= n; i++) {
this.controlPoints[i].x =
(i / (n + 1)) * this.controlPointsX[i - 1] +
((n - i + 1) / (n + 1)) * this.controlPointsX[i];
this.controlPoints[i].y =
(i / (n + 1)) * this.controlPointsY[i - 1] +
((n - i + 1) / (n + 1)) * this.controlPointsY[i];
}
for (let i = 0; i <= n; i++) {
this.controlPointsX[i] = this.controlPoints[i].x;
this.controlPointsY[i] = this.controlPoints[i].y;
}
this.addControlPoint(lastX, lastY);
}
changeGranularity(x) {
this.granularity = x;
this.t = linspace(0, 1, x);
}
addControlPoint(x, y) {
this.controlPoints.push(new BezierControlPoint(x, y));
this.controlPointsX.push(x);
this.controlPointsY.push(y);
let n = this.controlPoints.length;
//number of constructPoint (interpolating points) goes as triangular sequence
//0,1,3,6,10,15,... = n(n+1)/2
//for n= 0 (1 control points) -> 0 interpolating points
//for n= 1 (2 control points)-> 1 interpolating points
//for n= 2 (3 control points) -> 3 interpolating points
//for n = 3 (4 control points) -> 6 interpolating points
//and so on
for (
let i = 0;
i < (n * (n + 1)) / 2 - this.constructPoints.length - 1;
i++
) {
this.constructPoints.push(new ConstructPoint(x, y));
}
//number of constructLines goes as triangular sequence but with n-1 so (n-1)*(n)/2
//for n= 0 (1 control points) -> 0 interpolating lines
//for n= 1 (2 control points)-> 0 interpolating lines
//for n= 2 (3 control points) -> 1 interpolating lines
//for n = 3 (4 control points) -> 3 interpolating lines
//for n = 4 (5 control points) -> 6 interpolating lines
//and so on
for (
let i = 0;
i < ((n - 1) * n) / 2 - this.constructLines.length - 1;
i++
) {
this.constructLines.push(
new ConstructLine(
this.controlPoints[this.controlPoints.length - 2],
this.controlPoints[this.controlPoints.length - 3]
)
);
}
//add Control polygon lines
if (n > 1) {
this.controlPolygonLines.push(
new ConstructLine(
this.controlPoints[this.controlPoints.length - 2],
this.controlPoints[this.controlPoints.length - 1]
)
);
}
}
//change visibility of the control polygon
showControlPolygon() {
this.checkedShowControlPolygon = !this.checkedShowControlPolygon;
}
//change visibility of construct lines (interpolating lines)
showConstructLines() {
this.checkedShowConstructLines = !this.checkedShowConstructLines;
}
//change visibility of curve trace
showCurveTrace() {
this.checkedShowCurveTrace = !this.checkedShowCurveTrace;
}
//calculate de casteljau algorithm
deCasteljau(t) {
if (this.controlPoints.length == 0) {
return null;
}
//copy control points coordinate because with them moving, can't make
//in place replace
let controlPointsXCopy = [this.controlPointsX];
let controlPointsYCopy = [this.controlPointsY];
let k = 0;
let m = 0;
for (let i = 0; i < this.controlPoints.length - 1; i++) {
for (let j = 0; j < this.controlPoints.length - i - 1; j++) {
controlPointsXCopy[j] =
(1 - t) * controlPointsXCopy[j] + t * controlPointsXCopy[j + 1];
controlPointsYCopy[j] =
(1 - t) * controlPointsYCopy[j] + t * controlPointsYCopy[j + 1];
this.constructPoints[k].x = controlPointsXCopy[j];
this.constructPoints[k].y = controlPointsYCopy[j];
if (j > 0) {
this.constructLines[m].p1 = this.constructPoints[k];
this.constructLines[m].p2 = this.constructPoints[k - 1];
m += 1;
}
k += 1;
}
}
return [controlPointsXCopy[0], controlPointsYCopy[0]];
}
mousePressedAction() {
for (let i = 0; i < this.controlPoints.length; i++) {
let vertexUI = this.controlPoints[i];
if (vertexUI.hasInside(mouseX, mouseY)) {
this.draggedControlPointIndex = i;
}
}
}
mouseDraggedAction() {
if (this.draggedControlPointIndex == -1) return;
let newMouseX = mouseX;
let newMouseY = mouseY;
this.controlPoints[this.draggedControlPointIndex].x = newMouseX;
this.controlPoints[this.draggedControlPointIndex].y = newMouseY;
this.controlPointsX[this.draggedControlPointIndex] = newMouseX;
this.controlPointsY[this.draggedControlPointIndex] = newMouseY;
}
mouseReleasedAction() {
this.draggedControlPointIndex = -1;
}
render() {
for (let i = 0; i < this.controlPoints.length; i++) {
this.controlPoints[i].render();
}
//commented because this draws the whole curve without accounting the slider
/*for(let i = 0; i< this.t.length; i++){
let tmp = this.deCasteljau(this.t[i]);
if(tmp != null){
//console.log(tmp[0] + " " + tmp[1]);
stroke(0);
strokeWeight(2);
ellipse(tmp[0],tmp[1], 1, 1);
}
}*/
//the slider.value() != 0 removes imperfections in visualization. (overlapping lines or points not returning to the begininning)
if (slider.value() != 0 && slider.value() != sliderMax) {
for (let i = 0; i < this.constructPoints.length; i++) {
this.constructPoints[i].render();
}
}
if (
this.checkedShowConstructLines &&
slider.value() != 0 &&
slider.value() != sliderMax
) {
for (let i = 0; i < this.constructLines.length; i++) {
this.constructLines[i].render();
}
}
if (this.checkedShowControlPolygon) {
for (let i = 0; i < this.controlPolygonLines.length; i++) {
this.controlPolygonLines[i].render();
}
}
stroke("blue");
for (let i = 0;i < mapSpace(slider.value(), 0, 100, 0, this.granularity);i++) {
let tmp = this.deCasteljau(this.t[i]);
if (tmp != null && this.checkedShowCurveTrace) {
point(tmp[0], tmp[1]);
}
}
stroke(0);
}
}
class BezierControlPoint {
constructor(x = null, y = null) {
this.x = x;
this.y = y;
this.radius = 3;
this.grabbableRadius = 10;
}
render() {
stroke(0);
strokeWeight(4);
ellipse(this.x, this.y, this.radius * 2, this.radius * 2);
}
hasInside(x, y) {
let distance = dist(this.x, this.y, x, y);
return distance <= this.grabbableRadius;
}
}
function doubleClicked() {
bezierCurve.addControlPoint(mouseX, mouseY);
}
function mousePressed() {
bezierCurve.mousePressedAction();
}
function mouseDragged() {
bezierCurve.mouseDraggedAction();
}
function mouseReleased() {
bezierCurve.mouseReleasedAction();
}
function myEventCheckBoxShowControlPolygonLines() {
bezierCurve.showControlPolygon();
}
function myEventCheckBoxShowConstructLines() {
bezierCurve.showConstructLines();
}
function myEventCheckBoxShowCurveTrace() {
bezierCurve.showCurveTrace();
}
function myEventDegreeElevation() {
bezierCurve.degreeElevation();
}
function windowResized() {
resizeCanvas(windowWidth / 1.6, widowHeight / 1.6);
}