xxxxxxxxxx
456
var bezierCurve;
var slider; var sliderMax = 100;
var checkBoxAutoPlay; let checkedBoxAutoPlay = false; let addToSlider = 1;
var checkBoxShowConstructLines;
var checkBoxShowControlPolygonLines;
var chhckBoxShowCurveTrace;
var granularityInput,granularityButton;
var degreeElevationButton;
function setup() {
bezierCurve = new BezierCurve([[130,130,-20],[-110,80,-100],[20,-90,-20],[20,45,105]]);
frameRate(30);
var cnv = createCanvas(800, 800, WEBGL);
cnv.position(0,30)
cam1 = createCamera()
cam1.lookAt(150,170,140);
//debugMode(GRID);
colorMode(RGB);
angleMode(DEGREES);
stroke(0,0,0);
strokeWeight(4);
slider = createSlider(0, sliderMax, 1);
slider.position(10, 10);
slider.style('width', '200px');
slider.value(0);
checkBoxAutoPlay = createCheckbox('auto play', checkedBoxAutoPlay);
checkBoxAutoPlay.changed(myEventCheckBoxAutoPlay);
checkBoxAutoPlay.position(0,30)
checkBoxShowConstructLines = createCheckbox('show construct lines', true);
checkBoxShowConstructLines.changed(myEventCheckBoxShowConstructLines);
checkBoxShowConstructLines.position(0,50)
checkBoxShowControlPolygonLines = createCheckbox('show control polygon', true);
checkBoxShowControlPolygonLines.changed(myEventCheckBoxShowControlPolygonLines);
checkBoxShowControlPolygonLines.position(0,70)
//checkBoxShowCurveTrace = createCheckbox('show curve', true);
//checkBoxShowCurveTrace.changed(myEventCheckBoxShowCurveTrace);
granularityInput = createInput("10-1000");
granularityInput.position(400, 10);
granularityButton = createButton('granularity');
granularityButton.position(granularityInput.x + granularityInput.width, 10);
granularityButton.mousePressed(myEventChangeGranularity);
degreeElevationButton = createButton('degree elevation');
degreeElevationButton.position(0, 100);
degreeElevationButton.mousePressed(myEventDegreeElevation);
}
function draw() {
background(255,255);
//draw axis
//line(-150, 150, -150, 150, 150, -150);
//line(-150, -150, -150, -150, 150, -150);
//line(-150, 150, -150, -150, 150, 150);
if(checkedBoxAutoPlay){
if(slider.value() == sliderMax) addToSlider = -1;
if(slider.value() == 0 && addToSlider < 0 ) addToSlider = 1;
slider.value((slider.value()+addToSlider));
}
let objSelected = false;
// Pan: Cam rotation about y-axis (Left Right)
let azimuth = -atan2(cam1.eyeZ - cam1.centerZ, cam1.eyeX - cam1.centerX);
// Tilt: Cam rotation about z-axis (Up Down)
let zenith = -atan2(cam1.eyeY - cam1.centerY, dist(cam1.eyeX, cam1.eyeZ, cam1.centerX, cam1.centerZ));
// f is a scaling factor (depends on canvas size and camera perspective settings)
let f = height * 4.3 / 5;
let x = [-1, (mouseY - height/2)/f, -(mouseX - width/2)/f]
let R = math.multiply(Rz(-zenith), Ry(azimuth))
x = math.multiply(x, R)
let xMag = dist(0, 0, 0, x._data[0], x._data[1], x._data[2])
for(let i = 0; i < bezierCurve.controlPoints.length; i++){
let dToObj = dist(cam1.eyeX, cam1.eyeY, cam1.eyeZ, bezierCurve.controlPoints[i].x, bezierCurve.controlPoints[i].y, bezierCurve.controlPoints[i].z);
if(dist(cam1.eyeX + x._data[0] * dToObj / xMag,
cam1.eyeY + x._data[1] * dToObj / xMag,
cam1.eyeZ + x._data[2] * dToObj / xMag,
bezierCurve.controlPoints[i].x, bezierCurve.controlPoints[i].y, bezierCurve.controlPoints[i].z) < 20) {
let canMove = true;
for(let j=0;j<bezierCurve.controlPoints.length;j++){
if(i!=j && bezierCurve.controlPoints[j].selected) canMove = false;
}
if(mouseIsPressed && canMove){
objSelected = true;
bezierCurve.controlPoints[i].selected = true;
bezierCurve.controlPoints[i].x = cam1.eyeX + x._data[0] * dToObj / xMag;
bezierCurve.controlPointsX[i] = bezierCurve.controlPoints[i].x;
bezierCurve.controlPoints[i].y = cam1.eyeY + x._data[1] * dToObj / xMag;
bezierCurve.controlPointsY[i] = bezierCurve.controlPoints[i].y;
bezierCurve.controlPoints[i].z = cam1.eyeZ + x._data[2] * dToObj / xMag;
bezierCurve.controlPointsZ[i] = bezierCurve.controlPoints[i].z;
}else{
bezierCurve.controlPoints[i].selected = false;
objSelected = false;
}
}
}
if(!objSelected){
orbitControl(4,4);
}
bezierCurve.render();
}
// Rotation matrix for rotation about x-axis
function Rx(th) {
return math.matrix([[1, 0, 0],
[0, cos(th), -sin(th)],
[0, sin(th), cos(th)]
])
}
// Rotation matrix for rotation about y-axis
function Ry(th) {
return math.matrix([[cos(th), 0, -sin(th)],
[0, 1, 0],
[sin(th), 0, cos(th)]
])
}
// Rotation matrix for rotation about z-axis
function Rz(th) {
return math.matrix([[cos(th), sin(th), 0],
[-sin(th), cos(th), 0],
[0, 0, 1]])
}
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.p1.z,this.p2.x,this.p2.y,this.p2.z);
}
}
class ConstructPoint{
constructor(x = null, y = null, z = null) {
this.x = x
this.y = y
this.z = z;
this.highlighted = true;
this.selected = false; //used for moving points around
}
render() {
strokeWeight(5);
beginShape(POINTS);
vertex(this.x,this.y,this.z);
endShape();
}
}
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(points = []) {
//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.controlPointsZ = [];
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;
if(points.length != 0){
for(let i = 0;i<points.length; i++){
this.addControlPoint(points[i][0],points[i][1],points[i][2])
}
}
}
degreeElevation(){
let lastX, lastY, lastZ, n;
n = this.controlPoints.length-1;
lastX = this.controlPoints[n].x;
lastY = this.controlPoints[n].y;
lastZ = this.controlPoints[n].z;
//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];
this.controlPoints[i].z = i/(n+1) * this.controlPointsZ[i-1] + (n-i+1)/(n+1)*this.controlPointsZ[i];
}
for(let i=0;i<=n; i++){
this.controlPointsX[i] = this.controlPoints[i].x;
this.controlPointsY[i] = this.controlPoints[i].y;
this.controlPointsZ[i] = this.controlPoints[i].z;
}
this.addControlPoint(lastX,lastY,lastZ);
}
changeGranularity(x){
this.granularity = x;
this.t = linspace(0,1,x);
}
addControlPoint(x,y,z){
this.controlPoints.push(new ConstructPoint(x,y,z));
this.controlPointsX.push(x);
this.controlPointsY.push(y);
this.controlPointsZ.push(z);
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,z));
}
//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
calcBezierPoint(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 controlPointsZCopy = [this.controlPointsZ];
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];
controlPointsZCopy[j] = (1-t)*controlPointsZCopy[j] + t*controlPointsZCopy[j+1];
this.constructPoints[k].x = controlPointsXCopy[j];
this.constructPoints[k].y = controlPointsYCopy[j];
this.constructPoints[k].z = controlPointsZCopy[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], controlPointsZCopy[0]]
}
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.calcBezierPoint(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();
}
}
beginShape(POINTS);
stroke("blue");
for(let i = 0; i< mapSpace(slider.value(),0,100,0,this.granularity); i++){
let tmp = this.calcBezierPoint(this.t[i]);
if(tmp != null && this.checkedShowCurveTrace){
strokeWeight(2);
vertex(tmp[0],tmp[1],tmp[2]);
}
}
endShape();
stroke(0);
}
}
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);
}
function doubleClicked() {
bezierCurve.addControlPoint(0,0,0);
}
function myEventChangeGranularity(){
bezierCurve.changeGranularity(granularityInput.value());
}