@bluemath/geom
Version:
Bluemath Geometry library
1,135 lines (1,004 loc) • 31 kB
text/typescript
/*
Copyright (C) 2017 Jayesh Salvi, Blue Math Software Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {NDArray,AABB,length,dir,add,mul} from '@bluemath/common'
import {
BSplineCurve,BezierCurve,BezierSurface,BSplineSurface,
LineSegment, CircleArc, Circle
} from '../src/nurbs'
import {CoordSystem} from '../src'
const RESOLUTION = 50;
import {DATA} from './nurbs-data'
function generateBSplinePlotlyData3D(bcrv:BSplineCurve) {
let traces = [];
//let Nip = bcrv.tessellateBasis(RESOLUTION);
let u = new NDArray({shape:[RESOLUTION+1]});
for(let i=0; i<RESOLUTION+1; i++) {
u.set(i, i/RESOLUTION);
}
let tess = bcrv.tessellate(RESOLUTION);
traces.push({
x: Array.from(tess.getA(':',0).data),
y: Array.from(tess.getA(':',1).data),
z: Array.from(tess.getA(':',2).data),
xaxis : 'x1',
yaxis : 'y1',
type : 'scatter3d',
mode : 'lines',
name:'Curve'
});
traces.push({
x: Array.from(bcrv.cpoints.getA(':',0).data),
y: Array.from(bcrv.cpoints.getA(':',1).data),
z: Array.from(bcrv.cpoints.getA(':',2).data),
xaxis : 'x1',
yaxis : 'y1',
type : 'scatter3d',
mode : 'markers',
name:'Control Points'
});
/*
for(let i=0; i<Nip.shape[0]; i++) {
traces.push({
x : Array.from(u.data),
y : Array.from(Nip.get(i,':').data),
xaxis : 'x2',
yaxis : 'y2',
type:'scatter',
mode : 'lines',
name:`N(${i},${bcrv.degree})`
});
}
let ones = new NDArray({shape:[bcrv.knots.shape[0]]});
ones.fill(1);
traces.push({
x : Array.from(bcrv.knots.data),
y : Array.from(ones.data),
xaxis : 'x2',
yaxis : 'y2',
type : 'scatter',
mode : 'markers',
name : 'Knot Vector'
});
*/
return {traces};
}
function generateBSplinePlotlyData2D(bcrv:BSplineCurve) {
let D1_RESOLUTION = 10;
let aabb = bcrv.aabb();
let maxspan = Math.max(
Math.abs(aabb.max.getN(0) - aabb.min.getN(0)),
Math.abs(aabb.max.getN(1) - aabb.min.getN(1))
);
let darrowlength = maxspan * 0.1;
let traces = [];
let Nip = bcrv.tessellateBasis(RESOLUTION);
let u = new NDArray({shape:[RESOLUTION+1]});
for(let i=0; i<RESOLUTION+1; i++) {
u.set(i, i/RESOLUTION);
}
let tess = bcrv.tessellate(RESOLUTION);
let tessD = bcrv.tessellateDerivatives(D1_RESOLUTION, 3);
let tdshape = tessD.shape;
tessD = tessD.getA(':',1);
tessD.reshape([tdshape[0], tdshape[2]]);
let arrowShapes = [];
for(let i=0; i<D1_RESOLUTION+1; i++) {
let point = bcrv.evaluate(i/D1_RESOLUTION);
let d1dir = dir(tessD.getA(i));
let arrowHead = <NDArray>add(point, mul(d1dir,darrowlength));
arrowShapes.push({
type : 'line',
x0 : point.getN(0),
y0 : point.getN(1),
x1 : arrowHead.getN(0),
y1 : arrowHead.getN(1),
line : {
color : '#800',
width : 2
}
});
}
traces.push({
x: Array.from(tess.getA(':',0).data),
y: Array.from(tess.getA(':',1).data),
xaxis : 'x1',
yaxis : 'y1',
type : 'scatter',
mode : 'lines',
name:'Curve'
});
/*
traces.push({
x: Array.from(tessD.getA(':',0).data),
y: Array.from(tessD.getA(':',1).data),
xaxis : 'x1',
yaxis : 'y1',
type : 'scatter',
mode : 'markers',
visible : 'legendonly',
name:'1st Derivative'
});
*/
traces.push({
x: Array.from(bcrv.cpoints.getA(':',0).data),
y: Array.from(bcrv.cpoints.getA(':',1).data),
xaxis : 'x1',
yaxis : 'y1',
type : 'scatter',
mode : 'markers',
name:'Control Points'
});
for(let i=0; i<Nip.shape[0]; i++) {
traces.push({
x : Array.from(u.data),
y : Array.from(Nip.getA(i,':').data),
xaxis : 'x2',
yaxis : 'y2',
type:'scatter',
mode : 'lines',
name:`N(${i},${bcrv.degree})`
});
}
let ones = new NDArray({shape:[bcrv.knots.shape[0]]});
ones.fill(1);
traces.push({
x : Array.from(bcrv.knots.data),
y : Array.from(ones.data),
xaxis : 'x2',
yaxis : 'y2',
type : 'scatter',
mode : 'markers',
name : 'Knot Vector'
});
return {traces,shapes:arrowShapes};
}
function generateBSplinePlotlyData(bcrv : BSplineCurve) {
if(bcrv.dimension === 2) {
return generateBSplinePlotlyData2D(bcrv);
} else if(bcrv.dimension === 3) {
return generateBSplinePlotlyData3D(bcrv);
}
}
function generateBezierPlotlyData(bezcrv : BezierCurve) {
let traces = [];
// let tess = bezcrv.tessellate(RESOLUTION);
let tess = bezcrv.tessellateAdaptive(0.01);
if(bezcrv.dimension === 2) {
traces.push({
x: Array.from(tess.getA(':',0).data),
y: Array.from(tess.getA(':',1).data),
xaxis : 'x1',
yaxis : 'y1',
type : 'scatter',
mode : 'lines',
name:'Curve'
});
traces.push({
x: Array.from(bezcrv.cpoints.getA(':',0).data),
y: Array.from(bezcrv.cpoints.getA(':',1).data),
xaxis : 'x1',
yaxis : 'y1',
type : 'scatter',
mode : 'markers',
name:'Control Points'
});
} else if(bezcrv.dimension === 3) {
traces.push({
x: Array.from(tess.getA(':',0).data),
y: Array.from(tess.getA(':',1).data),
z: Array.from(tess.getA(':',2).data),
type : 'scatter3d',
mode : 'lines',
name:'Curve'
});
traces.push({
x: Array.from(bezcrv.cpoints.getA(':',0).data),
y: Array.from(bezcrv.cpoints.getA(':',1).data),
z: Array.from(bezcrv.cpoints.getA(':',2).data),
type : 'scatter3d',
mode : 'markers',
name:'Control Points'
});
}
return traces;
}
let PLOT_WIDTH = 500;
let GAP_FRACTION = 0.02;
const BEZCURVE_CONSTRUCTION_LAYOUT = {
width : 500,
height : 500,
margin : {},
};
const BEZSURF_CONSTRUCTION_LAYOUT = {
width : 700,
height : 700,
margin : {t:0,b:0},
};
const CURVE_CONSTRUCTION_LAYOUT = {
width : 500,
height : 500,
margin : {t:0,b:0},
xaxis: { anchor: 'y1' },
xaxis2: { anchor: 'y2' },
yaxis2: { domain: [0, 0.5-GAP_FRACTION] , title:'Parametric space'},
yaxis: { domain: [0.5+GAP_FRACTION, 1] , title:'Euclidean space'},
};
const CURVE_COMPARISION_LAYOUT = {
width : 500,
height : 500,
margin : {t:0,b:20},
xaxis: { anchor: 'y1', range:[-5,+5] },
xaxis2: { anchor: 'y2', range:[-5,+5] },
yaxis2: { domain: [0, 0.5-GAP_FRACTION], range:[-5,+5] },
yaxis: { domain: [0.5+GAP_FRACTION, 1], range:[-5,+5] },
};
const SURFACE_COMPARISION_LAYOUT = {
width : 600,
height : 1000,
margin : {t:0,b:0,l:10,r:10},
scene1 : {
domain : {
x : [0.0,1.0],
y : [0.0,0.5]
}
},
scene2 : {
domain : {
x : [0.0,1.0],
y : [0.5,1.0]
}
}
};
function displayBezierCurve(crvData) {
let pelem = $('#curve-viz #mainplot').get(0);
$('#curve-viz').show();
$('#action-viz').hide();
let {degree, cpoints, weights} = crvData;
let bezcrv = new BezierCurve(degree,new NDArray(cpoints),
weights ? new NDArray(weights) : null);
let traces = generateBezierPlotlyData(bezcrv);
Plotly.newPlot(pelem, traces, BEZCURVE_CONSTRUCTION_LAYOUT);
}
function plotBSplineCurve(bcrv:BSplineCurve) {
let pelem = $('#curve-viz #mainplot').get(0);
$('#curve-viz').show();
$('#action-viz').hide();
let {traces,shapes} = generateBSplinePlotlyData(bcrv);
if(shapes && false) {
CURVE_CONSTRUCTION_LAYOUT.shapes = shapes;
}
Plotly.newPlot(pelem, traces, CURVE_CONSTRUCTION_LAYOUT);
let knotzeros = new Array(bcrv.degree);
knotzeros.fill(0);
$('#knotsliders')
.empty();
$('#knotsliders')
.append($('<span></span>'))
.html('Knot Vector U');
$('#knotsliders').append('<br>');
$('#weights').empty();
if(bcrv.isRational()) {
$('#weights')
.append($('<span></span>'))
.html('Weights');
$('#weights').append('<br>');
let weightsEq = $('<div></div>').attr('id','eq');
for(let i=0; i<bcrv.weights.shape[0]; i++) {
weightsEq.append(
$('<span></span>')
.attr('id', `weight${i}`)
.text(''+<number>bcrv.weights.get(i))
);
}
$('#weights').append(weightsEq);
$("#weights > #eq > span").each(function () {
// read initial values from markup and remove that
var value = parseFloat($(this).text());
$(this).empty().slider({
value: value,
min : -2,
max : +10,
step : 0.1,
animate: true,
orientation: "vertical",
slide : function (ev, ui) {
let thisid = $(ev.target).attr('id');
let thisnum = parseInt(/weight(\d+)/.exec(thisid)[1]);
bcrv.setWeight(thisnum, ui.value);
Plotly.newPlot(pelem, generateBSplinePlotlyData(bcrv), CURVE_CONSTRUCTION_LAYOUT);
let handle = $(this).find('.ui-slider-handle');
$(handle).text(''+ui.value);
},
create : function () {
let handle = $(this).find('.ui-slider-handle');
$(handle).text($(this).slider('value').toFixed(2));
}
});
});
}
$('#knotsliders')
.append(
$('<span></span>')
.attr('id','clamped-zero-knots')
.text(knotzeros.join(','))
);
let knots = bcrv.knots.data;
for(let i=bcrv.degree+1; i<knots.length-bcrv.degree; i++) {
let jqelem = $('<div></div>')
.attr('id',`slider${i}`)
.addClass('knotslider')
.slider({
range : true,
min : 0,
max : 100,
values : [knots[i-1]*100, knots[i]*100],
slide : function (ev, ui) {
let thisid = $(this).attr('id');
let thisnum = parseInt(/slider(\d+)/.exec(thisid)[1]);
if(thisnum === bcrv.degree+1 && ui.handleIndex === 0) {
return false;
}
if(thisnum === knots.length-bcrv.degree-1 && ui.handleIndex === 1) {
return false;
}
let handles = $(this).find('.ui-slider-handle');
let needsUpdate = false;
if(ui.handleIndex === 0) {
let val = ui.values[0]/100;
let leftslider = $(`#slider${thisnum-1}`);
let leftvalues = leftslider.slider('values');
leftvalues[1] = ui.values[0];
leftslider.slider('option','values',leftvalues);
$(handles[0]).text(val.toFixed(2));
let leftsliderhandles =
$(`#slider${thisnum-1}>.ui-slider-handle`);
$(leftsliderhandles[1]).text(val.toFixed(2));
bcrv.setKnot(thisnum-1, val);
needsUpdate = needsUpdate || true;
}
if(ui.handleIndex === 1) {
let val = ui.values[1]/100;
let rightslider = $(`#slider${thisnum+1}`);
let rightvalues = rightslider.slider('values');
rightvalues[0] = ui.values[1];
rightslider.slider('option','values',rightvalues);
$(handles[1]).text(val.toFixed(2));
let rightsliderhandles =
$(`#slider${thisnum+1}>.ui-slider-handle`);
$(rightsliderhandles[0]).text(val.toFixed(2));
bcrv.setKnot(thisnum, val);
needsUpdate = needsUpdate || true;
}
if(needsUpdate) {
Plotly.newPlot(pelem, generateBSplinePlotlyData(bcrv), CURVE_CONSTRUCTION_LAYOUT);
}
},
create: function () {
let values = $(this).slider('values');
let handles = $(this).find('.ui-slider-handle');
$(handles[0]).text((values[0]/100).toFixed(2));
$(handles[1]).text((values[1]/100).toFixed(2));
}
});
$('#knotsliders').append(jqelem);
}
let knotones = new Array(bcrv.degree);
knotones.fill(1);
$('#knotsliders').append($('<span></span>')
.attr('id','clamped-one-knots')
.text(knotones.join(',')));
}
function displayBSplineCurve(crvData) {
if(!crvData.knots) {
// Assume it's a bezier curve
crvData.knots = [];
for(let i=0; i<=crvData.degree; i++) {
crvData.knots.push(0);
}
for(let i=0; i<=crvData.degree; i++) {
crvData.knots.push(1);
}
}
let {degree, cpoints, knots} = crvData;
let bcrv = new BSplineCurve(degree,
new NDArray(cpoints), new NDArray(knots),
crvData.weights ? new NDArray(crvData.weights) : undefined);
plotBSplineCurve(bcrv);
}
function displaySpecificGeometry(o) {
let bcrv;
switch(o.type) {
case "LineSegment":
bcrv = new LineSegment(o.from, o.to);
break;
case "CircleArc":
bcrv = new CircleArc(
new CoordSystem(o.coord.origin,o.coord.x,o.coord.z),
o.radius,o.start,o.end);
break;
case "Circle":
bcrv = new Circle(
new CoordSystem(o.coord.origin,o.coord.x,o.coord.z),
o.radius);
break;
}
plotBSplineCurve(bcrv);
}
function displayBezierSurface(bezsrfData) {
let pelem = $('#action-viz #mainplot').get(0);
$('#curve-viz').hide();
$('#action-viz').show();
let bezsrf = new BezierSurface(
bezsrfData.u_degree, bezsrfData.v_degree,
new NDArray(bezsrfData.cpoints));
let tess = bezsrf.tessellatePoints(10);
let traces = [];
let ures = tess.shape[0];
let vres = tess.shape[1];
let xdata = [];
let ydata = [];
let zdata = [];
for(let i=0; i<ures; i++) {
for(let j=0; j<vres; j++) {
let pt:NDArray = <NDArray>(tess.get(i,j));
xdata.push(<number>pt.get(0));
ydata.push(<number>pt.get(1));
zdata.push(<number>pt.get(2));
}
}
traces.push({
x: xdata,
y: ydata,
z: zdata,
xaxis : 'x1',
yaxis : 'y1',
type : 'scatter3d',
mode : 'markers',
name:'Bezier Surface'
});
let uncp = bezsrf.cpoints.shape[0];
let vncp = bezsrf.cpoints.shape[1];
let cxdata = [];
let cydata = [];
let czdata = [];
for(let i=0; i<uncp; i++) {
for(let j=0; j<vncp; j++) {
let cp:NDArray = <NDArray>(bezsrf.cpoints.get(i,j));
cxdata.push(<number>cp.get(0));
cydata.push(<number>cp.get(1));
czdata.push(<number>cp.get(2));
}
}
traces.push({
x: cxdata,
y: cydata,
z: czdata,
type : 'scatter3d',
mode : 'markers',
name:'Control Points'
});
Plotly.newPlot(pelem, traces, BEZSURF_CONSTRUCTION_LAYOUT);
}
function displayBSplineSurface(bsrfData) {
let pelem = $('#action-viz #mainplot').get(0);
let traces = [];
$('#curve-viz').hide();
$('#action-viz').show();
let bsrf = new BSplineSurface(
bsrfData.u_degree, bsrfData.v_degree,
new NDArray(bsrfData.u_knots), new NDArray(bsrfData.v_knots),
new NDArray(bsrfData.cpoints),
bsrfData.weights ? new NDArray(bsrfData.weights) : undefined);
let tess = bsrf.tessellatePoints(10);
let ures = tess.shape[0];
let vres = tess.shape[1];
let xdata = [];
let ydata = [];
let zdata = [];
for(let i=0; i<ures; i++) {
for(let j=0; j<vres; j++) {
let pt:NDArray = <NDArray>(tess.get(i,j));
xdata.push(<number>pt.get(0));
ydata.push(<number>pt.get(1));
zdata.push(<number>pt.get(2));
}
}
traces.push({
x: xdata,
y: ydata,
z: zdata,
xaxis : 'x1',
yaxis : 'y1',
type : 'scatter3d',
mode : 'markers',
name:'BSpline Surface'
});
let uncp = bsrf.cpoints.shape[0];
let vncp = bsrf.cpoints.shape[1];
let cxdata = [];
let cydata = [];
let czdata = [];
for(let i=0; i<uncp; i++) {
for(let j=0; j<vncp; j++) {
let cp:NDArray = <NDArray>(bsrf.cpoints.get(i,j));
cxdata.push(<number>cp.get(0));
cydata.push(<number>cp.get(1));
czdata.push(<number>cp.get(2));
}
}
traces.push({
x: cxdata,
y: cydata,
z: czdata,
type : 'scatter3d',
mode : 'markers',
name:'Control Points'
});
Plotly.newPlot(pelem, traces, BEZSURF_CONSTRUCTION_LAYOUT);
}
function displayCurveDecomposition(crvsrc, bcrvs, titles) {
let pelem = $('#action-viz #mainplot').get(0);
let traces = [];
let tessSource = crvsrc.tessellate(RESOLUTION);
traces.push({
x: Array.from(tessSource.get(':',0).data),
y: Array.from(tessSource.get(':',1).data),
xaxis : 'x1',
yaxis : 'y1',
type : 'scatter',
mode : 'lines',
name:'Curve'
});
traces.push({
x: Array.from(crvsrc.cpoints.get(':',0).data),
y: Array.from(crvsrc.cpoints.get(':',1).data),
xaxis : 'x1',
yaxis : 'y1',
type : 'scatter',
mode : 'markers',
name:'Control Points'
});
let aabbTotal = crvsrc.aabb();
let aabbTarget = new AABB(2);
for(let bcrv of bcrvs) {
let tess = bcrv.tessellate(RESOLUTION);
traces.push({
x: Array.from(tess.get(':',0).data),
y: Array.from(tess.get(':',1).data),
xaxis : 'x2',
yaxis : 'y2',
type : 'scatter',
mode : 'lines',
name:'Curve'
});
traces.push({
x: Array.from(bcrv.cpoints.get(':',0).data),
y: Array.from(bcrv.cpoints.get(':',1).data),
xaxis : 'x2',
yaxis : 'y2',
type : 'scatter',
mode : 'markers',
name:'Control Points'
});
aabbTarget.merge(bcrv.aabb());
}
aabbTotal.merge(aabbTarget);
let xmin = aabbTotal.min.get(0);
let ymin = aabbTotal.min.get(1);
let xmax = aabbTotal.max.get(0);
let ymax = aabbTotal.max.get(1);
let plotXRange = [xmin-0.1*Math.abs(xmin), xmax+0.1*Math.abs(xmax)];
let plotYRange = [ymin-0.1*Math.abs(ymin), ymax+0.1*Math.abs(ymax)];
CURVE_COMPARISION_LAYOUT.xaxis.range = plotXRange;
CURVE_COMPARISION_LAYOUT.xaxis2.range = plotXRange;
CURVE_COMPARISION_LAYOUT.yaxis.range = plotYRange;
CURVE_COMPARISION_LAYOUT.yaxis2.range = plotYRange;
if(titles) {
CURVE_COMPARISION_LAYOUT.yaxis.title = titles[0];
CURVE_COMPARISION_LAYOUT.yaxis2.title = titles[1];
} else {
CURVE_COMPARISION_LAYOUT.yaxis.title = undefined;
CURVE_COMPARISION_LAYOUT.yaxis2.title = undefined;
}
Plotly.newPlot(pelem, traces, CURVE_COMPARISION_LAYOUT);
}
function displaySurfaceDecomposition(srfsrc, bezsrfs) {
let pelem = $('#action-viz #mainplot').get(0);
let traces = [];
let tess = srfsrc.tessellatePoints(10);
let ures = tess.shape[0];
let vres = tess.shape[1];
let xdata = [];
let ydata = [];
let zdata = [];
for(let i=0; i<ures; i++) {
for(let j=0; j<vres; j++) {
let pt:NDArray = <NDArray>(tess.get(i,j));
xdata.push(<number>pt.get(0));
ydata.push(<number>pt.get(1));
zdata.push(<number>pt.get(2));
}
}
traces.push({
x: xdata,
y: ydata,
z: zdata,
type : 'scatter3d',
mode : 'markers',
name : 'Source Surface',
scene:'scene1'
});
for(let bezsrf of bezsrfs) {
let tess = bezsrf.tessellatePoints(10);
let ures = tess.shape[0];
let vres = tess.shape[1];
let xdata = [];
let ydata = [];
let zdata = [];
for(let i=0; i<ures; i++) {
for(let j=0; j<vres; j++) {
let pt:NDArray = <NDArray>(tess.get(i,j));
xdata.push(<number>pt.get(0));
ydata.push(<number>pt.get(1));
zdata.push(<number>pt.get(2));
}
}
traces.push({
x: xdata,
y: ydata,
z: zdata,
type : 'scatter3d',
mode : 'markers',
scene:'scene2',
name:'Control Points'
});
/*
let uncp = bezsrf.cpoints.shape[0];
let vncp = bezsrf.cpoints.shape[1];
let cxdata = [];
let cydata = [];
let czdata = [];
for(let i=0; i<uncp; i++) {
for(let j=0; j<vncp; j++) {
let cp:NDArray = <NDArray>(bezsrf.cpoints.get(i,j));
cxdata.push(<number>cp.get(0));
cydata.push(<number>cp.get(1));
czdata.push(<number>cp.get(2));
}
}
traces.push({
x: cxdata,
y: cydata,
z: czdata,
type : 'scatter3d',
mode : 'markers',
scene:'scene2',
name:'Control Points'
});
*/
}
Plotly.newPlot(pelem, traces, SURFACE_COMPARISION_LAYOUT);
}
function displayCurveComparision(crvsrc, crvtgt, titles) {
let pelem = $('#action-viz #mainplot').get(0);
let traces = [];
let tessSource = crvsrc.tessellate(RESOLUTION);
traces.push({
x: Array.from(tessSource.get(':',0).data),
y: Array.from(tessSource.get(':',1).data),
xaxis : 'x1',
yaxis : 'y1',
type : 'scatter',
mode : 'lines',
name:'Curve'
});
traces.push({
x: Array.from(crvsrc.cpoints.get(':',0).data),
y: Array.from(crvsrc.cpoints.get(':',1).data),
xaxis : 'x1',
yaxis : 'y1',
type : 'scatter',
mode : 'markers',
name:'Control Points'
});
let aabbTotal = crvsrc.aabb();
let tgt;
if(Array.isArray(crvtgt)) {
tgt = crvtgt;
} else {
tgt = [crvtgt];
}
let aabbTarget = new AABB(2);
for(let crv of tgt) {
let tessTarget = crv.tessellate(RESOLUTION);
traces.push({
x: Array.from(tessTarget.get(':',0).data),
y: Array.from(tessTarget.get(':',1).data),
xaxis : 'x2',
yaxis : 'y2',
type : 'scatter',
mode : 'lines',
name:'Curve'
});
traces.push({
x: Array.from(crv.cpoints.get(':',0).data),
y: Array.from(crv.cpoints.get(':',1).data),
xaxis : 'x2',
yaxis : 'y2',
type : 'scatter',
mode : 'markers',
name:'Control Points'
});
aabbTarget.merge(crv.aabb());
}
aabbTotal.merge(aabbTarget);
let xmin = aabbTotal.min.get(0);
let ymin = aabbTotal.min.get(1);
let xmax = aabbTotal.max.get(0);
let ymax = aabbTotal.max.get(1);
let plotXRange = [xmin-0.1*Math.abs(xmin), xmax+0.1*Math.abs(xmax)];
let plotYRange = [ymin-0.1*Math.abs(ymin), ymax+0.1*Math.abs(ymax)];
CURVE_COMPARISION_LAYOUT.xaxis.range = plotXRange;
CURVE_COMPARISION_LAYOUT.xaxis2.range = plotXRange;
CURVE_COMPARISION_LAYOUT.yaxis.range = plotYRange;
CURVE_COMPARISION_LAYOUT.yaxis2.range = plotYRange;
if(titles) {
CURVE_COMPARISION_LAYOUT.yaxis.title = titles[0];
CURVE_COMPARISION_LAYOUT.yaxis2.title = titles[1];
} else {
CURVE_COMPARISION_LAYOUT.yaxis.title = undefined;
CURVE_COMPARISION_LAYOUT.yaxis2.title = undefined;
}
Plotly.newPlot(pelem, traces, CURVE_COMPARISION_LAYOUT);
}
function displaySurfaceComparision(srfsrc, srftgt, titles) {
let pelem = $('#action-viz #mainplot').get(0);
let traces = [];
let tess = srfsrc.tessellatePoints(10);
let ures = tess.shape[0];
let vres = tess.shape[1];
let xdata = [];
let ydata = [];
let zdata = [];
for(let i=0; i<ures; i++) {
for(let j=0; j<vres; j++) {
let pt:NDArray = <NDArray>(tess.get(i,j));
xdata.push(<number>pt.get(0));
ydata.push(<number>pt.get(1));
zdata.push(<number>pt.get(2));
}
}
traces.push({
x: xdata,
y: ydata,
z: zdata,
type : 'scatter3d',
mode : 'markers',
visible : 'legendonly',
name : titles[0]+' Surface',
scene:'scene1'
});
let uncp = srfsrc.cpoints.shape[0];
let vncp = srfsrc.cpoints.shape[1];
let cxdata = [];
let cydata = [];
let czdata = [];
for(let i=0; i<uncp; i++) {
for(let j=0; j<vncp; j++) {
let cp:NDArray = <NDArray>(srfsrc.cpoints.get(i,j));
cxdata.push(<number>cp.get(0));
cydata.push(<number>cp.get(1));
czdata.push(<number>cp.get(2));
}
}
traces.push({
x: cxdata,
y: cydata,
z: czdata,
type : 'scatter3d',
mode : 'markers',
name : titles[0]+' Control Points',
scene:'scene1'
});
tess = srftgt.tessellatePoints(10);
ures = tess.shape[0];
vres = tess.shape[1];
xdata = [];
ydata = [];
zdata = [];
for(let i=0; i<ures; i++) {
for(let j=0; j<vres; j++) {
let pt:NDArray = <NDArray>(tess.get(i,j));
xdata.push(<number>pt.get(0));
ydata.push(<number>pt.get(1));
zdata.push(<number>pt.get(2));
}
}
traces.push({
x: xdata,
y: ydata,
z: zdata,
type : 'scatter3d',
mode : 'markers',
visible : 'legendonly',
name:titles[1]+' Surface',
scene:'scene2'
});
uncp = srftgt.cpoints.shape[0];
vncp = srftgt.cpoints.shape[1];
let cxdata2 = [];
let cydata2 = [];
let czdata2 = [];
for(let i=0; i<uncp; i++) {
for(let j=0; j<vncp; j++) {
let cp:NDArray = <NDArray>(srftgt.cpoints.get(i,j));
cxdata2.push(<number>cp.get(0));
cydata2.push(<number>cp.get(1));
czdata2.push(<number>cp.get(2));
}
}
traces.push({
x: cxdata2,
y: cydata2,
z: czdata2,
type : 'scatter3d',
mode : 'markers',
name : titles[1]+' Control Points',
scene : 'scene2'
});
Plotly.newPlot(pelem, traces, SURFACE_COMPARISION_LAYOUT);
}
function performAction(actionData) {
$('#curve-viz').hide();
$('#action-viz').show();
if(actionData.actiontype === 'split_bezier') {
let crvdef = DATA_MAP[nameToKey(actionData.input)].object;
let crvSource = new BezierCurve(crvdef.degree,
new NDArray(crvdef.cpoints),
crvdef.weights ? new NDArray(crvdef.weights):undefined);
let splitCurves = [];
for(let interval of actionData.split_intervals) {
let crv = crvSource.clone();
crv.reparam(interval[0], interval[1]);
splitCurves.push(crv);
}
displayCurveComparision(crvSource, splitCurves,
['Original Bezier','Split Bezier curves']);
} else if(actionData.actiontype === 'insert_knot_curve') {
let crvdef = DATA_MAP[nameToKey(actionData.input)].object;
let crvSource = new BSplineCurve(crvdef.degree,
new NDArray(crvdef.cpoints), new NDArray(crvdef.knots),
crvdef.weights ? new NDArray(crvdef.weights) : undefined);
let crvTarget = crvSource.clone();
let un = actionData['knot_to_insert'];
let r = actionData['num_insertions'];
crvTarget.insertKnot(un, r);
displayCurveComparision(crvSource, crvTarget,
['Before Knot Insertion','After Knot Insertion']);
} else if(actionData.actiontype === 'refine_knot_curve') {
let crvdef = DATA_MAP[nameToKey(actionData.input)].object;
let crvSource;
crvSource = new BSplineCurve(crvdef.degree,
new NDArray(crvdef.cpoints), new NDArray(crvdef.knots),
crvdef.weights ? new NDArray(crvdef.weights) : undefined);
let crvTarget = crvSource.clone();
let uklist = actionData['knots_to_add'];
crvTarget.refineKnots(uklist);
displayCurveComparision(crvSource, crvTarget,
['Before Knot Refinement','After Knot Refinement']);
} else if(actionData.actiontype === 'decompose_curve') {
let crvdef = DATA_MAP[nameToKey(actionData.input)].object;
let crvSource = new BSplineCurve(crvdef.degree,
new NDArray(crvdef.cpoints), new NDArray(crvdef.knots),
crvdef.weights ? new NDArray(crvdef.weights) : undefined);
let bezcrvs = crvSource.decompose();
displayCurveDecomposition(crvSource, bezcrvs,
['Original','Decomposed Bezier pieces']);
} else if(actionData.actiontype === 'split_curve') {
let crvdef = DATA_MAP[nameToKey(actionData.input)].object;
let crvSource = new BSplineCurve(crvdef.degree,
new NDArray(crvdef.cpoints), new NDArray(crvdef.knots),
crvdef.weights ? new NDArray(crvdef.weights) : undefined);
let splitCurves = crvSource.split(actionData.parameter);
displayCurveDecomposition(crvSource, splitCurves,
['Original','Split BSpline curves']);
} else if(actionData.actiontype === 'decompose_surf') {
let srfdef = DATA_MAP[nameToKey(actionData.input)].object;
let srfSource = new BSplineSurface(srfdef.u_degree,srfdef.v_degree,
srfdef.u_knots, srfdef.v_knots,
srfdef.cpoints
);
let bezsrfs = srfSource.decompose();
displaySurfaceDecomposition(srfSource, bezsrfs);
} else if(actionData.actiontype === 'insert_knot_surf') {
let srfdef = DATA_MAP[nameToKey(actionData.input)].object;
let srfSource = new BSplineSurface(
srfdef.u_degree, srfdef.v_degree,
new NDArray(srfdef.u_knots), new NDArray(srfdef.v_knots),
new NDArray(srfdef.cpoints),
srfdef.weights ? new NDArray(srfdef.weights) : undefined
);
let srfTarget = srfSource.clone();
if(actionData.u_knot_to_insert !== undefined) {
srfTarget.insertKnotU(
actionData.u_knot_to_insert,actionData.num_insertions_u);
} else if(actionData.v_knot_to_insert !== undefined) {
srfTarget.insertKnotV(
actionData.v_knot_to_insert,actionData.num_insertions_v);
} else {
console.assert(false);
}
displaySurfaceComparision(srfSource, srfTarget,
['Before knot insertion','After knot insertion']);
} else if(actionData.actiontype === 'refine_knot_surf') {
let srfdef = DATA_MAP[nameToKey(actionData.input)].object;
let srfSource = new BSplineSurface(
srfdef.u_degree, srfdef.v_degree,
new NDArray(srfdef.u_knots), new NDArray(srfdef.v_knots),
new NDArray(srfdef.cpoints),
srfdef.weights ? new NDArray(srfdef.weights) : undefined
);
let srfTarget = srfSource.clone();
if(actionData.u_knots_to_add !== undefined) {
srfTarget.refineKnotsU(actionData.u_knots_to_add);
} else if(actionData.v_knots_to_add !== undefined) {
srfTarget.refineKnotsV(actionData.v_knots_to_add);
} else {
console.assert(false);
}
displaySurfaceComparision(srfSource, srfTarget,
['Before knot refinement','After knot refinement']);
}
}
function nameToKey(name) {
return name.replace(/[\(\)\s]+/g,'-').toLowerCase();
}
let DATA_MAP = {};
window.onload = () => {
for(let i=0; i<DATA.length; i++) {
let entry = DATA[i];
let key = nameToKey(entry.name);
$('#geom-selection').append(
$('<option></option>').val(key).html(entry.name));
DATA_MAP[key] = entry;
}
$('#geom-selection').on('change', ev => {
let choice = $('#geom-selection option:selected').val();
window.location.href =
window.location.protocol + '//' +
window.location.host + window.location.pathname + '#' + choice;
window.location.reload(true);
});
let urlmatch = /#([\d\w-]+)$/.exec(window.location.href);
let curChoice;
if(urlmatch) {
curChoice = urlmatch[1];
$('#geom-selection').val(''+curChoice);
} else {
curChoice = $('#geom-selection option:selected').val();
}
let data = DATA_MAP[curChoice];
if(data.type === 'BezierCurve') {
displayBezierCurve(data.object);
} else if(data.type === 'BSplineCurve') {
displayBSplineCurve(data.object);
} else if(data.type === 'SpecificGeometry') {
displaySpecificGeometry(data.object);
} else if(data.type === 'BezSurf') {
displayBezierSurface(data.object);
} else if(data.type === 'BSurf') {
displayBSplineSurface(data.object);
} else if(data.type === 'Action') {
performAction(data.object);
}
};