xxxxxxxxxx
393
var bezierSurface;
var sliderGranularityU, sliderGranularityV;
var checkBoxShowControlPoints, checkBoxShowControlNet;
let ctrl_pts;
function setup() {
//bicubic patch n=m=3
ctrl_pts = [[[0, 0, 20],[60, 0, -35],[90, 0, 60],[200, 0, 5],],[[0, 50, 30],[100, 60, -25],[120, 50, 120],[200, 50, 5],],[[0, 100, 0],[60, 120, 35],[90, 100, 60],[200, 100, 45],],[[0, 150, 0],[60, 150, -35],[90, 180, 60],[200, 150, 45],],];
//biquadratic patch n=m=2
//ctrl_pts = [[[0, 0, 20],[60, 0, -35],[90, 0, 60],],[[0, 50, 30],[100, 60, -25],[120, 50, 120],],[[0, 100, 0],[60, 120, 35],[90, 100, 60],],];
//bilinear patch n=m=1: every ispoarametric curve is a straight line
//ctrl_pts = [[[0, 0, 20],[60, 0, -35],],[[0, 50, 30],[100, 60, -25],],];
bezierSurface = new BezierSurface(ctrl_pts);
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);
sliderGranularityU = createSlider(10,50,10);
sliderGranularityU.position(0,0);
sliderGranularityV = createSlider(10,50,10);
sliderGranularityV.position(sliderGranularityU.width + 10,0)
checkBoxShowControlPoints = createCheckbox("show control points",true);
checkBoxShowControlPoints.position(0,20);
checkBoxShowControlNet = createCheckbox("show control net", true);
checkBoxShowControlNet.position(0,40);
}
function draw() {
background(255, 255);
// 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]);
let objSelected = false;
for (let i = 0; i < bezierSurface.controlPoints.length; i++) {
let dToObj = dist(
cam1.eyeX,
cam1.eyeY,
cam1.eyeZ,
bezierSurface.controlPoints[i].x,
bezierSurface.controlPoints[i].y,
bezierSurface.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,
bezierSurface.controlPoints[i].x,
bezierSurface.controlPoints[i].y,
bezierSurface.controlPoints[i].z
) < 20
) {
let canMove = true;
for(let j=0;j<bezierSurface.controlPoints.length;j++){
if(i!=j && bezierSurface.controlPoints[j].selected) canMove = false;
}
if (mouseIsPressed && canMove) {
objSelected = true;
bezierSurface.controlPoints[i].selected = true;
bezierSurface.controlPoints[i].x =
cam1.eyeX + (x._data[0] * dToObj) / xMag;
bezierSurface.controlPoints[i].y =
cam1.eyeY + (x._data[1] * dToObj) / xMag;
bezierSurface.controlPoints[i].z =
cam1.eyeZ + (x._data[2] * dToObj) / xMag;
let j = ctrl_pts.length;
let idx = math.floor(i/j);
bezierSurface.bezierCurvesV[idx].controlPoints[i%j].x =
bezierSurface.controlPoints[i].x;
bezierSurface.bezierCurvesV[idx].controlPointsX[i%j] =
bezierSurface.controlPoints[i].x;
bezierSurface.bezierCurvesV[idx].controlPoints[i%j].y =
bezierSurface.controlPoints[i].y;
bezierSurface.bezierCurvesV[idx].controlPointsY[i%j] =
bezierSurface.controlPoints[i].y;
bezierSurface.bezierCurvesV[idx].controlPoints[i%j].z =
bezierSurface.controlPoints[i].z;
bezierSurface.bezierCurvesV[idx].controlPointsZ[i%j] =
bezierSurface.controlPoints[i].z;
} else {
objSelected = false;
bezierSurface.controlPoints[i].selected = false;
}
}
}
if (!objSelected) {
orbitControl(4, 4);
}
bezierSurface.render();
bezierSurface.changeGranularityU(sliderGranularityU.value());
bezierSurface.changeGranularityV(sliderGranularityV.value());
}
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;
}
class ConstructPoint {
constructor(x = null, y = null, z = null, isControlPoint = false) {
this.x = x;
this.y = y;
this.z = z;
this.selected = false; //used for moving point around
this.isControlPoint = isControlPoint;
}
render() {
if (this.isControlPoint) {
push();
translate(this.x, this.y, this.z);
sphere(1);
pop();
} else {
strokeWeight(5);
beginShape(POINTS);
vertex(this.x, this.y, this.z);
endShape();
}
}
}
class BezierSurface {
constructor(points = []) {
this.controlPoints = [];
this.showControlPoints = true;
this.showControlNet = true;
this.u = 10;
this.v = 10;
this.bezierCurvesV = [];
this.bezierCurvesU = [];
if (points.length != 0) {
for (let i = 0; i < points.length; i++) {
for (let j = 0; j < points[0].length; j++) {
this.addControlPoint(points[i][j][0],points[i][j][1],points[i][j][2],true);
}
}
for (let i = 0; i < points.length; i++) {
let bz = new BezierCurve(points[i]);
bz.showCurve = false;
bz.changeGranularity(this.v);
this.bezierCurvesV.push(bz);
}
for (let j = 0; j < this.v; j++) {
let bz = new BezierCurve();
bz.changeGranularity(this.u);
this.bezierCurvesU.push(bz);
}
}
}
changeGranularityU(newU){
this.u = newU;
}
changeGranularityV(newV){
this.v = newV;
}
addControlPoint(x, y, z, isControlPoint = false) {
this.controlPoints.push(new ConstructPoint(x, y, z, isControlPoint));
}
render() {
for (let i = 0; i < this.controlPoints.length; i++) {
if(checkBoxShowControlPoints.checked()){
this.controlPoints[i].render();
}
if(checkBoxShowControlNet.checked()){
let coloumnsNumber = this.bezierCurvesV.length;
if(i%coloumnsNumber != 0){ line(this.controlPoints[i].x,this.controlPoints[i].y,this.controlPoints[i].z,this.controlPoints[i-1].x,this.controlPoints[i-1].y,this.controlPoints[i-1].z)
}
if(i>=coloumnsNumber){
line(this.controlPoints[i].x,this.controlPoints[i].y,this.controlPoints[i].z,this.controlPoints[i-coloumnsNumber].x,this.controlPoints[i-coloumnsNumber].y,this.controlPoints[i-coloumnsNumber].z)
}
}
}
for (let i = 0; i < this.bezierCurvesV.length; i++) {
this.bezierCurvesV[i].changeGranularity(this.v);
this.bezierCurvesV[i].render();
}
for (let j = 0; j < this.v; j++) {
let cpoints = [];
for (let k = 0; k < this.bezierCurvesV[0].controlPoints.length; k++) {
cpoints.push([this.bezierCurvesV[k].points[j][0],this.bezierCurvesV[k].points[j][1],this.bezierCurvesV[k].points[j][2]]);
}
this.bezierCurvesU[j] = new BezierCurve(cpoints)
this.bezierCurvesU[j].changeGranularity(this.u);
this.bezierCurvesU[j].showCurve = false;
this.bezierCurvesU[j].render();
}
stroke("blue");
for(let j=0;j<this.v-1;j++){
for(let i=0;i<this.bezierCurvesU[j].points.length-1;i++){
beginShape(QUAD_STRIP);
vertex(this.bezierCurvesU[j].points[i][0],this.bezierCurvesU[j].points[i][1],this.bezierCurvesU[j].points[i][2]);
vertex(this.bezierCurvesU[j+1].points[i][0],this.bezierCurvesU[j+1].points[i][1],this.bezierCurvesU[j+1].points[i][2]);
vertex(this.bezierCurvesU[j].points[i+1][0],this.bezierCurvesU[j].points[i+1][1],this.bezierCurvesU[j].points[i+1][2]);
vertex(this.bezierCurvesU[j+1].points[i+1][0],this.bezierCurvesU[j+1].points[i+1][1],this.bezierCurvesU[j+1].points[i+1][2])
endShape();
}
}
stroke(0);
}
}
// 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],
]);
}
let id = 0;
class BezierCurve {
constructor(points = []) {
//TODO add the possibility to create a curve passing arguments
this.id = id;
id += 1;
this.showCurve = true;
this.rendered = false;
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.points = Array(this.granularity).fill(0);
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]);
}
}
}
changeGranularity(x) {
this.granularity = x;
this.t = linspace(0, 1, x);
this.points = Array(x).fill(0);
}
addControlPoint(x, y, z) {
this.controlPoints.push(new ConstructPoint(x, y, z));
this.controlPointsX.push(x);
this.controlPointsY.push(y);
this.controlPointsZ.push(z);
}
//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];
k += 1;
}
}
return [
controlPointsXCopy[0],
controlPointsYCopy[0],
controlPointsZCopy[0],
];
}
render() {
if (this.showCurve) {
beginShape(POINTS);
for (let i = 0; i < this.t.length; i++) {
let tmp = this.calcBezierPoint(this.t[i]);
if (tmp != null) {
strokeWeight(2);
vertex(tmp[0], tmp[1], tmp[2]);
this.points[i] = [tmp[0], tmp[1], tmp[2]];
}
}
endShape();
} else{
for (let i = 0; i < this.t.length; i++) {
let tmp = this.calcBezierPoint(this.t[i]);
if (tmp != null) {
this.points[i] = [tmp[0], tmp[1], tmp[2]];
}
}
}
}
}