jsketcher/modules/geom/surfaces/nurbsTiling.js
2022-08-15 23:47:20 -07:00

244 lines
No EOL
6.1 KiB
JavaScript

import Vector from 'math/vector';
class Tree {
constructor() {
this.children = null;
}
isLeaf() {
return this.children == null;
}
leafs(callback) {
if (this.isLeaf()) {
callback(this);
} else {
for (const child of this.children) {
child.leafs(callback);
}
}
}
}
export class Tile extends Tree {
constructor(surface, bottom, right, top, left) {
super();
this.surface = surface;
this.edges = [bottom, right, top, left]
this.edges.forEach(e => e.tile = this);
}
divideHalf(vert) {
const shift = vert ? 0 : 1;
const bottom = this.edges[0 + shift];
const right = this.edges[1 + shift];
const up = this.edges[2 + shift];
const left = this.edges[(3 + shift) % 4];
const bottomDivs = bottom.split();
const upDivs = up.split();
const newEdge = new TileEdge(bottomDivs[0].b, upDivs[0].b);
if (vert) {
this.children = [
new Tile(this.surface, bottomDivs[0], newEdge, upDivs[1], left),
new Tile(this.surface, bottomDivs[1], right, upDivs[0], newEdge.twin),
];
} else {
this.children = [
new Tile(this.surface, left, bottomDivs[0], newEdge, upDivs[1]),
new Tile(this.surface, newEdge.twin, bottomDivs[1], right, upDivs[0]),
];
}
}
divideVertically() {
this.divideHalf(true);
}
divideHorizontally() {
this.divideHalf(false);
}
divideBoth() {
throw 'not implemented'
}
center() {
if (!this._center) {
this._center = new UVPoint(mid(this.edges[0].a.u, this.edges[0].b.u), mid(this.edges[1].a.v, this.edges[1].b.v), this.surface);
}
return this._center;
}
}
export class TileEdge extends Tree {
constructor(a, b, outer, twin) {
super();
this.tile = null; // to be set by a Tile
this.a = a;
this.b = b;
this.outer = outer;
if (twin == undefined) {
twin = new TileEdge(b, a, outer, this);
}
this.twin = twin;
}
split() {
if (this.children == null) {
const midPoint = new UVPoint(mid(this.a.u, this.b.u), mid(this.a.v, this.b.v), this.tile.surface);
this.children = [
new TileEdge(this.a, midPoint, this.outer),
new TileEdge(midPoint, this.b, this.outer),
];
this.twin.children = [
this.children[1].twin,
this.children[0].twin
]
}
return this.children;
}
}
class UVPoint {
constructor(u, v, surface) {
this.u = u;
this.v = v;
this.surface = surface;
}
normal() {
if (!this._normal) {
this._normal = new Vector().set3(this.surface.normal(this.u, this.v))._normalize();
}
return this._normal;
}
}
export function initTiles(surface, opts) {
const data = surface._data;
let nSplitsU = (data.controlPoints.length - 1);
let nSplitsV = (data.controlPoints[0].length - 1);
if (opts.maxUSplits && nSplitsU > opts.maxUSplits) {
nSplitsU = opts.maxUSplits;
}
if (opts.maxVSplits && nSplitsV > opts.maxVSplits) {
nSplitsV = opts.maxVSplits;
}
const umax = data.knotsU[data.knotsU.length - 1];
const umin = data.knotsU[0];
const vmax = data.knotsV[data.knotsV.length - 1];
const vmin = data.knotsV[0];
const du = (umax - umin) / nSplitsU;
const dv = (vmax - vmin) / nSplitsV;
const table = [];
for (let vIdx = 0; vIdx < nSplitsV + 1; ++vIdx) {
const row = [];
table.push(row);
for (let uIdx = 0; uIdx < nSplitsU + 1; ++uIdx) {
const u = umin + du * uIdx;
const v = vmin + dv * vIdx;
row.push(new UVPoint(u, v, surface));
}
if (row.length <= 1) {
return [];
}
}
if (table.length <= 1) {
return [];
}
const tiles = [];
for (let vIdx = 0; vIdx < nSplitsV; ++vIdx) {
const row = [];
tiles.push(row);
for (let uIdx = 0; uIdx < nSplitsU; ++uIdx) {
const bottomOuter = vIdx == 0 ? 'bottom' : undefined;
const leftOuter = uIdx == 0 ? 'left' : undefined;
const topOuter = vIdx == nSplitsV - 1 ? 'top' : undefined;
const rightOuter = uIdx == nSplitsU - 1 ? 'right' : undefined;
const left = uIdx != 0 ? row[uIdx - 1].edges[1].twin : new TileEdge(table[vIdx+1][uIdx], table[vIdx][uIdx], leftOuter);
const bottom = vIdx != 0 ? tiles[vIdx - 1][uIdx].edges[2].twin : new TileEdge(table[vIdx][uIdx], table[vIdx][uIdx+1], bottomOuter);
const right = new TileEdge(table[vIdx][uIdx+1], table[vIdx+1][uIdx+1], rightOuter);
const top = new TileEdge(table[vIdx+1][uIdx+1], table[vIdx+1][uIdx], topOuter);
row.push(new Tile(surface, bottom, right, top, left));
}
}
return tiles;
}
function mid(a, b) {
return a == b ? a : ((a + b) / 2.0);
}
export function refine(tiles, opts) {
opts = opts || {};
const uMax = opts.uMax === undefined ? 10 : opts.uMax;
const vMax = opts.vMax === undefined ? 10 : opts.vMax;
const normTol = 8.5e-2;
function curvature(a, b) {
return a.normal().minus(b.normal()).lengthSquared();
}
function edgeCurvature(e) {
return curvature(e.a, e.b);
}
function check(tile, uLevel, vLevel) {
const horizLimit = vLevel >= vMax;
const vertLimit = uLevel >= uMax;
const splitHoriz = !horizLimit && edgeCurvature(tile.edges[1]) > normTol || edgeCurvature(tile.edges[3]) > normTol;
let splitVert = false;
if (!splitHoriz && !vertLimit) {
splitVert = edgeCurvature(tile.edges[0]) > normTol || edgeCurvature(tile.edges[2]) > normTol;
if (!splitVert) {
const center = tile.center();
splitVert =
curvature(center, tile.edges[0].a) > normTol ||
curvature(center, tile.edges[1].a) > normTol ||
curvature(center, tile.edges[2].a) > normTol ||
curvature(center, tile.edges[3].a) > normTol;
}
}
if (splitHoriz) {
tile.divideHorizontally();
vLevel ++;
} else if (splitVert) {
tile.divideVertically();
uLevel ++;
}
if (tile.children != null) {
for (const subTile of tile.children) {
check(subTile, uLevel, vLevel);
}
}
}
for (const row of tiles) {
for (const tile of row) {
check(tile, 0, 0);
}
}
}