jsroot
Version:
JavaScript ROOT
1,414 lines (1,171 loc) • 140 kB
JavaScript
import { isObject, isFunc, BIT } from '../core.mjs';
import { THREE } from '../base/base3d.mjs';
import { createBufferGeometry, createNormal,
Vertex as CsgVertex, Geometry as CsgGeometry, Polygon as CsgPolygon } from './csg.mjs';
const _cfg = {
GradPerSegm: 6, // grad per segment in cylinder/spherical symmetry shapes
CompressComp: true // use faces compression in composite shapes
};
/** @summary Returns or set geometry config values
* @desc Supported 'GradPerSegm' and 'CompressComp'
* @private */
function geoCfg(name, value) {
if (value === undefined)
return _cfg[name];
_cfg[name] = value;
}
const kindGeo = 0, // TGeoNode / TGeoShape
kindEve = 1, // TEveShape / TEveGeoShapeExtract
kindShape = 2, // special kind for single shape handling
/** @summary TGeo-related bits
* @private */
geoBITS = {
kVisOverride: BIT(0), // volume's vis. attributes are overwritten
kVisNone: BIT(1), // the volume/node is invisible, as well as daughters
kVisThis: BIT(2), // this volume/node is visible
kVisDaughters: BIT(3), // all leaves are visible
kVisOneLevel: BIT(4), // first level daughters are visible (not used)
kVisStreamed: BIT(5), // true if attributes have been streamed
kVisTouched: BIT(6), // true if attributes are changed after closing geom
kVisOnScreen: BIT(7), // true if volume is visible on screen
kVisContainers: BIT(12), // all containers visible
kVisOnly: BIT(13), // just this visible
kVisBranch: BIT(14), // only a given branch visible
kVisRaytrace: BIT(15) // raytracing flag
},
clTGeoBBox = 'TGeoBBox',
clTGeoArb8 = 'TGeoArb8',
clTGeoCone = 'TGeoCone',
clTGeoConeSeg = 'TGeoConeSeg',
clTGeoTube = 'TGeoTube',
clTGeoTubeSeg = 'TGeoTubeSeg',
clTGeoCtub = 'TGeoCtub',
clTGeoTrd1 = 'TGeoTrd1',
clTGeoTrd2 = 'TGeoTrd2',
clTGeoPara = 'TGeoPara',
clTGeoParaboloid = 'TGeoParaboloid',
clTGeoPcon = 'TGeoPcon',
clTGeoPgon = 'TGeoPgon',
clTGeoShapeAssembly = 'TGeoShapeAssembly',
clTGeoSphere = 'TGeoSphere',
clTGeoTorus = 'TGeoTorus',
clTGeoXtru = 'TGeoXtru',
clTGeoTrap = 'TGeoTrap',
clTGeoGtra = 'TGeoGtra',
clTGeoEltu = 'TGeoEltu',
clTGeoHype = 'TGeoHype',
clTGeoCompositeShape = 'TGeoCompositeShape',
clTGeoHalfSpace = 'TGeoHalfSpace',
clTGeoScaledShape = 'TGeoScaledShape';
/** @summary Test fGeoAtt bits
* @private */
function testGeoBit(volume, f) {
const att = volume.fGeoAtt;
return att === undefined ? false : ((att & f) !== 0);
}
/** @summary Set fGeoAtt bit
* @private */
function setGeoBit(volume, f, value) {
if (volume.fGeoAtt === undefined) return;
volume.fGeoAtt = value ? (volume.fGeoAtt | f) : (volume.fGeoAtt & ~f);
}
/** @summary Toggle fGeoAttBit
* @private */
function toggleGeoBit(volume, f) {
if (volume.fGeoAtt !== undefined)
volume.fGeoAtt ^= f & 0xffffff;
}
/** @summary Implementation of TGeoVolume::InvisibleAll
* @private */
function setInvisibleAll(volume, flag) {
if (flag === undefined) flag = true;
setGeoBit(volume, geoBITS.kVisThis, !flag);
// setGeoBit(this, geoBITS.kVisDaughters, !flag);
if (volume.fNodes) {
for (let n = 0; n < volume.fNodes.arr.length; ++n) {
const sub = volume.fNodes.arr[n].fVolume;
setGeoBit(sub, geoBITS.kVisThis, !flag);
// setGeoBit(sub, geoBITS.kVisDaughters, !flag);
}
}
}
const _warn_msgs = {};
/** @summary method used to avoid duplication of warnings
* @private */
function geoWarn(msg) {
if (_warn_msgs[msg] !== undefined) return;
_warn_msgs[msg] = true;
console.warn(msg);
}
/** @summary Analyze TGeo node kind
* @desc 0 - TGeoNode
* 1 - TEveGeoNode
* -1 - unsupported
* @return detected node kind
* @private */
function getNodeKind(obj) {
if (!isObject(obj)) return -1;
return ('fShape' in obj) && ('fTrans' in obj) ? kindEve : kindGeo;
}
/** @summary Returns number of shapes
* @desc Used to count total shapes number in composites
* @private */
function countNumShapes(shape) {
if (!shape) return 0;
if (shape._typename !== clTGeoCompositeShape) return 1;
return countNumShapes(shape.fNode.fLeft) + countNumShapes(shape.fNode.fRight);
}
/** @summary Returns geo object name
* @desc Can appends some special suffixes
* @private */
function getObjectName(obj) {
return obj?.fName ? (obj.fName + (obj.$geo_suffix || '')) : '';
}
/** @summary Check duplicates
* @private */
function checkDuplicates(parent, chlds) {
if (parent) {
if (parent.$geo_checked) return;
parent.$geo_checked = true;
}
const names = [], cnts = [];
for (let k = 0; k < chlds.length; ++k) {
const chld = chlds[k];
if (!chld?.fName) continue;
if (!chld.$geo_suffix) {
const indx = names.indexOf(chld.fName);
if (indx >= 0) {
let cnt = cnts[indx] || 1;
while (names.indexOf(chld.fName+'#'+cnt) >= 0) ++cnt;
chld.$geo_suffix = '#' + cnt;
cnts[indx] = cnt+1;
}
}
names.push(getObjectName(chld));
}
}
/** @summary Create normal to plane, defined with three points
* @private */
function produceNormal(x1, y1, z1, x2, y2, z2, x3, y3, z3) {
const pA = new THREE.Vector3(x1, y1, z1),
pB = new THREE.Vector3(x2, y2, z2),
pC = new THREE.Vector3(x3, y3, z3),
cb = new THREE.Vector3(),
ab = new THREE.Vector3();
cb.subVectors(pC, pB);
ab.subVectors(pA, pB);
cb.cross(ab);
return cb;
}
// ==========================================================================
/**
* @summary Helper class for geometry creation
*
* @private
*/
class GeometryCreator {
/** @summary Constructor
* @param numfaces - number of faces */
constructor(numfaces) {
this.nfaces = numfaces;
this.indx = 0;
this.pos = new Float32Array(numfaces*9);
this.norm = new Float32Array(numfaces*9);
}
/** @summary Add face with 3 vertices */
addFace3(x1, y1, z1, x2, y2, z2, x3, y3, z3) {
const indx = this.indx, pos = this.pos;
pos[indx] = x1;
pos[indx+1] = y1;
pos[indx+2] = z1;
pos[indx+3] = x2;
pos[indx+4] = y2;
pos[indx+5] = z2;
pos[indx+6] = x3;
pos[indx+7] = y3;
pos[indx+8] = z3;
this.last4 = false;
this.indx = indx + 9;
}
/** @summary Start polygon */
startPolygon() {}
/** @summary Stop polygon */
stopPolygon() {}
/** @summary Add face with 4 vertices
* @desc From four vertices one normally creates two faces (1,2,3) and (1,3,4)
* if (reduce === 1), first face is reduced
* if (reduce === 2), second face is reduced */
addFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, reduce) {
let indx = this.indx;
const pos = this.pos;
if (reduce !== 1) {
pos[indx] = x1;
pos[indx+1] = y1;
pos[indx+2] = z1;
pos[indx+3] = x2;
pos[indx+4] = y2;
pos[indx+5] = z2;
pos[indx+6] = x3;
pos[indx+7] = y3;
pos[indx+8] = z3;
indx+=9;
}
if (reduce !== 2) {
pos[indx] = x1;
pos[indx+1] = y1;
pos[indx+2] = z1;
pos[indx+3] = x3;
pos[indx+4] = y3;
pos[indx+5] = z3;
pos[indx+6] = x4;
pos[indx+7] = y4;
pos[indx+8] = z4;
indx+=9;
}
this.last4 = (indx !== this.indx + 9);
this.indx = indx;
}
/** @summary Specify normal for face with 4 vertices
* @desc same as addFace4, assign normals for each individual vertex
* reduce has same meaning and should be the same */
setNormal4(nx1, ny1, nz1, nx2, ny2, nz2, nx3, ny3, nz3, nx4, ny4, nz4, reduce) {
if (this.last4 && reduce)
return console.error('missmatch between addFace4 and setNormal4 calls');
let indx = this.indx - (this.last4 ? 18 : 9);
const norm = this.norm;
if (reduce !== 1) {
norm[indx] = nx1;
norm[indx+1] = ny1;
norm[indx+2] = nz1;
norm[indx+3] = nx2;
norm[indx+4] = ny2;
norm[indx+5] = nz2;
norm[indx+6] = nx3;
norm[indx+7] = ny3;
norm[indx+8] = nz3;
indx+=9;
}
if (reduce !== 2) {
norm[indx] = nx1;
norm[indx+1] = ny1;
norm[indx+2] = nz1;
norm[indx+3] = nx3;
norm[indx+4] = ny3;
norm[indx+5] = nz3;
norm[indx+6] = nx4;
norm[indx+7] = ny4;
norm[indx+8] = nz4;
}
}
/** @summary Recalculate Z with provided func */
recalcZ(func) {
const pos = this.pos,
last = this.indx;
let indx = last - (this.last4 ? 18 : 9);
while (indx < last) {
pos[indx+2] = func(pos[indx], pos[indx+1], pos[indx+2]);
indx+=3;
}
}
/** @summary Calculate normal */
calcNormal() {
if (!this.cb) {
this.pA = new THREE.Vector3();
this.pB = new THREE.Vector3();
this.pC = new THREE.Vector3();
this.cb = new THREE.Vector3();
this.ab = new THREE.Vector3();
}
this.pA.fromArray(this.pos, this.indx - 9);
this.pB.fromArray(this.pos, this.indx - 6);
this.pC.fromArray(this.pos, this.indx - 3);
this.cb.subVectors(this.pC, this.pB);
this.ab.subVectors(this.pA, this.pB);
this.cb.cross(this.ab);
this.setNormal(this.cb.x, this.cb.y, this.cb.z);
}
/** @summary Set normal */
setNormal(nx, ny, nz) {
let indx = this.indx - 9;
const norm = this.norm;
norm[indx] = norm[indx+3] = norm[indx+6] = nx;
norm[indx+1] = norm[indx+4] = norm[indx+7] = ny;
norm[indx+2] = norm[indx+5] = norm[indx+8] = nz;
if (this.last4) {
indx -= 9;
norm[indx] = norm[indx+3] = norm[indx+6] = nx;
norm[indx+1] = norm[indx+4] = norm[indx+7] = ny;
norm[indx+2] = norm[indx+5] = norm[indx+8] = nz;
}
}
/** @summary Set normal
* @desc special shortcut, when same normals can be applied for 1-2 point and 3-4 point */
setNormal_12_34(nx12, ny12, nz12, nx34, ny34, nz34, reduce) {
if (reduce === undefined) reduce = 0;
let indx = this.indx - ((reduce > 0) ? 9 : 18);
const norm = this.norm;
if (reduce !== 1) {
norm[indx] = nx12;
norm[indx+1] = ny12;
norm[indx+2] = nz12;
norm[indx+3] = nx12;
norm[indx+4] = ny12;
norm[indx+5] = nz12;
norm[indx+6] = nx34;
norm[indx+7] = ny34;
norm[indx+8] = nz34;
indx += 9;
}
if (reduce !== 2) {
norm[indx] = nx12;
norm[indx+1] = ny12;
norm[indx+2] = nz12;
norm[indx+3] = nx34;
norm[indx+4] = ny34;
norm[indx+5] = nz34;
norm[indx+6] = nx34;
norm[indx+7] = ny34;
norm[indx+8] = nz34;
}
}
/** @summary Create geometry */
create() {
if (this.nfaces !== this.indx/9)
console.error(`Mismatch with created ${this.nfaces} and filled ${this.indx/9} number of faces`);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(this.pos, 3));
geometry.setAttribute('normal', new THREE.BufferAttribute(this.norm, 3));
return geometry;
}
}
// ================================================================================
/** @summary Helper class for CsgGeometry creation
*
* @private
*/
class PolygonsCreator {
/** @summary constructor */
constructor() {
this.polygons = [];
}
/** @summary Start polygon */
startPolygon(normal) {
this.multi = 1;
this.mnormal = normal;
}
/** @summary Stop polygon */
stopPolygon() {
if (!this.multi) return;
this.multi = 0;
console.error('Polygon should be already closed at this moment');
}
/** @summary Add face with 3 vertices */
addFace3(x1, y1, z1, x2, y2, z2, x3, y3, z3) {
this.addFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x3, y3, z3, 2);
}
/** @summary Add face with 4 vertices
* @desc From four vertices one normally creates two faces (1,2,3) and (1,3,4)
* if (reduce === 1), first face is reduced
* if (reduce === 2), second face is reduced */
addFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, reduce) {
if (reduce === undefined) reduce = 0;
this.v1 = new CsgVertex(x1, y1, z1, 0, 0, 0);
this.v2 = (reduce === 1) ? null : new CsgVertex(x2, y2, z2, 0, 0, 0);
this.v3 = new CsgVertex(x3, y3, z3, 0, 0, 0);
this.v4 = (reduce === 2) ? null : new CsgVertex(x4, y4, z4, 0, 0, 0);
this.reduce = reduce;
if (this.multi) {
if (reduce !== 2)
console.error('polygon not supported for not-reduced faces');
let polygon;
if (this.multi++ === 1) {
polygon = new CsgPolygon();
polygon.vertices.push(this.mnormal ? this.v2 : this.v3);
this.polygons.push(polygon);
} else {
polygon = this.polygons.at(-1);
// check that last vertex equals to v2
const last = this.mnormal ? polygon.vertices.at(-1) : polygon.vertices.at(0),
comp = this.mnormal ? this.v2 : this.v3;
if (comp.diff(last) > 1e-12)
console.error('vertex missmatch when building polygon');
}
const first = this.mnormal ? polygon.vertices[0] : polygon.vertices.at(-1),
next = this.mnormal ? this.v3 : this.v2;
if (next.diff(first) < 1e-12)
this.multi = 0;
else if (this.mnormal)
polygon.vertices.push(this.v3);
else
polygon.vertices.unshift(this.v2);
return;
}
const polygon = new CsgPolygon();
switch (reduce) {
case 0: polygon.vertices.push(this.v1, this.v2, this.v3, this.v4); break;
case 1: polygon.vertices.push(this.v1, this.v3, this.v4); break;
case 2: polygon.vertices.push(this.v1, this.v2, this.v3); break;
}
this.polygons.push(polygon);
}
/** @summary Specify normal for face with 4 vertices
* @desc same as addFace4, assign normals for each individual vertex
* reduce has same meaning and should be the same */
setNormal4(nx1, ny1, nz1, nx2, ny2, nz2, nx3, ny3, nz3, nx4, ny4, nz4) {
this.v1.setnormal(nx1, ny1, nz1);
if (this.v2) this.v2.setnormal(nx2, ny2, nz2);
this.v3.setnormal(nx3, ny3, nz3);
if (this.v4) this.v4.setnormal(nx4, ny4, nz4);
}
/** @summary Set normal
* @desc special shortcut, when same normals can be applied for 1-2 point and 3-4 point */
setNormal_12_34(nx12, ny12, nz12, nx34, ny34, nz34) {
this.v1.setnormal(nx12, ny12, nz12);
if (this.v2) this.v2.setnormal(nx12, ny12, nz12);
this.v3.setnormal(nx34, ny34, nz34);
if (this.v4) this.v4.setnormal(nx34, ny34, nz34);
}
/** @summary Calculate normal */
calcNormal() {
if (!this.cb) {
this.pA = new THREE.Vector3();
this.pB = new THREE.Vector3();
this.pC = new THREE.Vector3();
this.cb = new THREE.Vector3();
this.ab = new THREE.Vector3();
}
this.pA.set(this.v1.x, this.v1.y, this.v1.z);
if (this.reduce !== 1) {
this.pB.set(this.v2.x, this.v2.y, this.v2.z);
this.pC.set(this.v3.x, this.v3.y, this.v3.z);
} else {
this.pB.set(this.v3.x, this.v3.y, this.v3.z);
this.pC.set(this.v4.x, this.v4.y, this.v4.z);
}
this.cb.subVectors(this.pC, this.pB);
this.ab.subVectors(this.pA, this.pB);
this.cb.cross(this.ab);
this.setNormal(this.cb.x, this.cb.y, this.cb.z);
}
/** @summary Set normal */
setNormal(nx, ny, nz) {
this.v1.setnormal(nx, ny, nz);
if (this.v2) this.v2.setnormal(nx, ny, nz);
this.v3.setnormal(nx, ny, nz);
if (this.v4) this.v4.setnormal(nx, ny, nz);
}
/** @summary Recalculate Z with provided func */
recalcZ(func) {
this.v1.z = func(this.v1.x, this.v1.y, this.v1.z);
if (this.v2) this.v2.z = func(this.v2.x, this.v2.y, this.v2.z);
this.v3.z = func(this.v3.x, this.v3.y, this.v3.z);
if (this.v4) this.v4.z = func(this.v4.x, this.v4.y, this.v4.z);
}
/** @summary Create geometry
* @private */
create() {
return { polygons: this.polygons };
}
}
// ================= all functions to create geometry ===================================
/** @summary Creates cube geometry
* @private */
function createCubeBuffer(shape, faces_limit) {
if (faces_limit < 0) return 12;
const dx = shape.fDX, dy = shape.fDY, dz = shape.fDZ,
creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(12);
creator.addFace4(dx, dy, dz, dx, -dy, dz, dx, -dy, -dz, dx, dy, -dz); creator.setNormal(1, 0, 0);
creator.addFace4(-dx, dy, -dz, -dx, -dy, -dz, -dx, -dy, dz, -dx, dy, dz); creator.setNormal(-1, 0, 0);
creator.addFace4(-dx, dy, -dz, -dx, dy, dz, dx, dy, dz, dx, dy, -dz); creator.setNormal(0, 1, 0);
creator.addFace4(-dx, -dy, dz, -dx, -dy, -dz, dx, -dy, -dz, dx, -dy, dz); creator.setNormal(0, -1, 0);
creator.addFace4(-dx, dy, dz, -dx, -dy, dz, dx, -dy, dz, dx, dy, dz); creator.setNormal(0, 0, 1);
creator.addFace4(dx, dy, -dz, dx, -dy, -dz, -dx, -dy, -dz, -dx, dy, -dz); creator.setNormal(0, 0, -1);
return creator.create();
}
/** @summary Creates 8 edges geometry
* @private */
function create8edgesBuffer(v, faces_limit) {
const indicies = [4, 7, 6, 5, 0, 3, 7, 4, 4, 5, 1, 0, 6, 2, 1, 5, 7, 3, 2, 6, 1, 2, 3, 0],
creator = (faces_limit > 0) ? new PolygonsCreator() : new GeometryCreator(12);
for (let n = 0; n < indicies.length; n += 4) {
const i1 = indicies[n]*3,
i2 = indicies[n+1]*3,
i3 = indicies[n+2]*3,
i4 = indicies[n+3]*3;
creator.addFace4(v[i1], v[i1+1], v[i1+2], v[i2], v[i2+1], v[i2+2],
v[i3], v[i3+1], v[i3+2], v[i4], v[i4+1], v[i4+2]);
if (n === 0)
creator.setNormal(0, 0, 1);
else if (n === 20)
creator.setNormal(0, 0, -1);
else
creator.calcNormal();
}
return creator.create();
}
/** @summary Creates PARA geometry
* @private */
function createParaBuffer(shape, faces_limit) {
if (faces_limit < 0) return 12;
const txy = shape.fTxy, txz = shape.fTxz, tyz = shape.fTyz, v = [
-shape.fZ*txz-txy*shape.fY-shape.fX, -shape.fY-shape.fZ*tyz, -shape.fZ,
-shape.fZ*txz+txy*shape.fY-shape.fX, shape.fY-shape.fZ*tyz, -shape.fZ,
-shape.fZ*txz+txy*shape.fY+shape.fX, shape.fY-shape.fZ*tyz, -shape.fZ,
-shape.fZ*txz-txy*shape.fY+shape.fX, -shape.fY-shape.fZ*tyz, -shape.fZ,
shape.fZ*txz-txy*shape.fY-shape.fX, -shape.fY+shape.fZ*tyz, shape.fZ,
shape.fZ*txz+txy*shape.fY-shape.fX, shape.fY+shape.fZ*tyz, shape.fZ,
shape.fZ*txz+txy*shape.fY+shape.fX, shape.fY+shape.fZ*tyz, shape.fZ,
shape.fZ*txz-txy*shape.fY+shape.fX, -shape.fY+shape.fZ*tyz, shape.fZ];
return create8edgesBuffer(v, faces_limit);
}
/** @summary Creates trapezoid geometry
* @private */
function createTrapezoidBuffer(shape, faces_limit) {
if (faces_limit < 0) return 12;
let y1, y2;
if (shape._typename === clTGeoTrd1)
y1 = y2 = shape.fDY;
else {
y1 = shape.fDy1; y2 = shape.fDy2;
}
const v = [
-shape.fDx1, y1, -shape.fDZ,
shape.fDx1, y1, -shape.fDZ,
shape.fDx1, -y1, -shape.fDZ,
-shape.fDx1, -y1, -shape.fDZ,
-shape.fDx2, y2, shape.fDZ,
shape.fDx2, y2, shape.fDZ,
shape.fDx2, -y2, shape.fDZ,
-shape.fDx2, -y2, shape.fDZ
];
return create8edgesBuffer(v, faces_limit);
}
/** @summary Creates arb8 geometry
* @private */
function createArb8Buffer(shape, faces_limit) {
if (faces_limit < 0) return 12;
const vertices = [
shape.fXY[0][0], shape.fXY[0][1], -shape.fDZ,
shape.fXY[1][0], shape.fXY[1][1], -shape.fDZ,
shape.fXY[2][0], shape.fXY[2][1], -shape.fDZ,
shape.fXY[3][0], shape.fXY[3][1], -shape.fDZ,
shape.fXY[4][0], shape.fXY[4][1], shape.fDZ,
shape.fXY[5][0], shape.fXY[5][1], shape.fDZ,
shape.fXY[6][0], shape.fXY[6][1], shape.fDZ,
shape.fXY[7][0], shape.fXY[7][1], shape.fDZ
],
indicies = [
4, 7, 6, 6, 5, 4, 3, 7, 4, 4, 0, 3,
5, 1, 0, 0, 4, 5, 6, 2, 1, 1, 5, 6,
7, 3, 2, 2, 6, 7, 1, 2, 3, 3, 0, 1];
// detect same vertices on both Z-layers
for (let side = 0; side < vertices.length; side += vertices.length/2) {
for (let n1 = side; n1 < side + vertices.length/2 - 3; n1+=3) {
for (let n2 = n1+3; n2 < side + vertices.length/2; n2+=3) {
if ((vertices[n1] === vertices[n2]) &&
(vertices[n1+1] === vertices[n2+1]) &&
(vertices[n1+2] === vertices[n2+2])) {
for (let k=0; k<indicies.length; ++k)
if (indicies[k] === n2/3) indicies[k] = n1/3;
}
}
}
}
const map = []; // list of existing faces (with all rotations)
let numfaces = 0;
for (let k = 0; k < indicies.length; k += 3) {
const id1 = indicies[k]*100 + indicies[k+1]*10 + indicies[k+2],
id2 = indicies[k+1]*100 + indicies[k+2]*10 + indicies[k],
id3 = indicies[k+2]*100 + indicies[k]*10 + indicies[k+1];
if ((indicies[k] === indicies[k+1]) || (indicies[k] === indicies[k+2]) || (indicies[k+1] === indicies[k+2]) ||
(map.indexOf(id1) >= 0) || (map.indexOf(id2) >= 0) || (map.indexOf(id3) >= 0))
indicies[k] = indicies[k+1] = indicies[k+2] = -1;
else {
map.push(id1, id2, id3);
numfaces++;
}
}
const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces);
for (let n = 0; n < indicies.length; n += 6) {
const i1 = indicies[n] * 3,
i2 = indicies[n+1] * 3,
i3 = indicies[n+2] * 3,
i4 = indicies[n+3] * 3,
i5 = indicies[n+4] * 3,
i6 = indicies[n+5] * 3;
let norm = null;
if ((i1 >= 0) && (i4 >= 0) && faces_limit) {
// try to identify two faces with same normal - very useful if one can create face4
if (n === 0)
norm = new THREE.Vector3(0, 0, 1);
else if (n === 30)
norm = new THREE.Vector3(0, 0, -1);
else {
const norm1 = produceNormal(vertices[i1], vertices[i1+1], vertices[i1+2],
vertices[i2], vertices[i2+1], vertices[i2+2],
vertices[i3], vertices[i3+1], vertices[i3+2]);
norm1.normalize();
const norm2 = produceNormal(vertices[i4], vertices[i4+1], vertices[i4+2],
vertices[i5], vertices[i5+1], vertices[i5+2],
vertices[i6], vertices[i6+1], vertices[i6+2]);
norm2.normalize();
if (norm1.distanceToSquared(norm2) < 1e-12) norm = norm1;
}
}
if (norm !== null) {
creator.addFace4(vertices[i1], vertices[i1+1], vertices[i1+2],
vertices[i2], vertices[i2+1], vertices[i2+2],
vertices[i3], vertices[i3+1], vertices[i3+2],
vertices[i5], vertices[i5+1], vertices[i5+2]);
creator.setNormal(norm.x, norm.y, norm.z);
} else {
if (i1 >= 0) {
creator.addFace3(vertices[i1], vertices[i1+1], vertices[i1+2],
vertices[i2], vertices[i2+1], vertices[i2+2],
vertices[i3], vertices[i3+1], vertices[i3+2]);
creator.calcNormal();
}
if (i4 >= 0) {
creator.addFace3(vertices[i4], vertices[i4+1], vertices[i4+2],
vertices[i5], vertices[i5+1], vertices[i5+2],
vertices[i6], vertices[i6+1], vertices[i6+2]);
creator.calcNormal();
}
}
}
return creator.create();
}
/** @summary Creates sphere geometry
* @private */
function createSphereBuffer(shape, faces_limit) {
const radius = [shape.fRmax, shape.fRmin],
phiStart = shape.fPhi1,
phiLength = shape.fPhi2 - shape.fPhi1,
thetaStart = shape.fTheta1,
thetaLength = shape.fTheta2 - shape.fTheta1,
noInside = (radius[1] <= 0);
let widthSegments = shape.fNseg,
heightSegments = shape.fNz;
if (faces_limit > 0) {
const fact = (noInside ? 2 : 4) * widthSegments * heightSegments / faces_limit;
if (fact > 1.0) {
widthSegments = Math.max(4, Math.floor(widthSegments/Math.sqrt(fact)));
heightSegments = Math.max(4, Math.floor(heightSegments/Math.sqrt(fact)));
}
}
let numoutside = widthSegments * heightSegments * 2,
numtop = widthSegments * (noInside ? 1 : 2),
numbottom = widthSegments * (noInside ? 1 : 2);
const numcut = (phiLength === 360) ? 0 : heightSegments * (noInside ? 2 : 4),
epsilon = 1e-10;
if (faces_limit < 0) return numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut;
const _sinp = new Float32Array(widthSegments+1),
_cosp = new Float32Array(widthSegments+1),
_sint = new Float32Array(heightSegments+1),
_cost = new Float32Array(heightSegments+1);
for (let n = 0; n <= heightSegments; ++n) {
const theta = (thetaStart + thetaLength/heightSegments*n)*Math.PI/180;
_sint[n] = Math.sin(theta);
_cost[n] = Math.cos(theta);
}
for (let n = 0; n <= widthSegments; ++n) {
const phi = (phiStart + phiLength/widthSegments*n)*Math.PI/180;
_sinp[n] = Math.sin(phi);
_cosp[n] = Math.cos(phi);
}
if (Math.abs(_sint[0]) <= epsilon) { numoutside -= widthSegments; numtop = 0; }
if (Math.abs(_sint[heightSegments]) <= epsilon) { numoutside -= widthSegments; numbottom = 0; }
const numfaces = numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut,
creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces);
for (let side = 0; side < 2; ++side) {
if ((side === 1) && noInside) break;
const r = radius[side],
s = (side === 0) ? 1 : -1,
d1 = 1 - side, d2 = 1 - d1;
// use direct algorithm for the sphere - here normals and position can be calculated directly
for (let k = 0; k < heightSegments; ++k) {
const k1 = k + d1, k2 = k + d2;
let skip = 0;
if (Math.abs(_sint[k1]) <= epsilon) skip = 1; else
if (Math.abs(_sint[k2]) <= epsilon) skip = 2;
for (let n = 0; n < widthSegments; ++n) {
creator.addFace4(
r*_sint[k1]*_cosp[n], r*_sint[k1] *_sinp[n], r*_cost[k1],
r*_sint[k1]*_cosp[n+1], r*_sint[k1] *_sinp[n+1], r*_cost[k1],
r*_sint[k2]*_cosp[n+1], r*_sint[k2] *_sinp[n+1], r*_cost[k2],
r*_sint[k2]*_cosp[n], r*_sint[k2] *_sinp[n], r*_cost[k2],
skip);
creator.setNormal4(
s*_sint[k1]*_cosp[n], s*_sint[k1] *_sinp[n], s*_cost[k1],
s*_sint[k1]*_cosp[n+1], s*_sint[k1] *_sinp[n+1], s*_cost[k1],
s*_sint[k2]*_cosp[n+1], s*_sint[k2] *_sinp[n+1], s*_cost[k2],
s*_sint[k2]*_cosp[n], s*_sint[k2] *_sinp[n], s*_cost[k2],
skip);
}
}
}
// top/bottom
for (let side = 0; side <= heightSegments; side += heightSegments) {
if (Math.abs(_sint[side]) >= epsilon) {
const ss = _sint[side], cc = _cost[side],
d1 = (side === 0) ? 0 : 1, d2 = 1 - d1;
for (let n = 0; n < widthSegments; ++n) {
creator.addFace4(
radius[1] * ss * _cosp[n+d1], radius[1] * ss * _sinp[n+d1], radius[1] * cc,
radius[0] * ss * _cosp[n+d1], radius[0] * ss * _sinp[n+d1], radius[0] * cc,
radius[0] * ss * _cosp[n+d2], radius[0] * ss * _sinp[n+d2], radius[0] * cc,
radius[1] * ss * _cosp[n+d2], radius[1] * ss * _sinp[n+d2], radius[1] * cc,
noInside ? 2 : 0);
creator.calcNormal();
}
}
}
// cut left/right sides
if (phiLength < 360) {
for (let side = 0; side <= widthSegments; side += widthSegments) {
const ss = _sinp[side], cc = _cosp[side],
d1 = (side === 0) ? 1 : 0, d2 = 1 - d1;
for (let k=0; k<heightSegments; ++k) {
creator.addFace4(
radius[1] * _sint[k+d1] * cc, radius[1] * _sint[k+d1] * ss, radius[1] * _cost[k+d1],
radius[0] * _sint[k+d1] * cc, radius[0] * _sint[k+d1] * ss, radius[0] * _cost[k+d1],
radius[0] * _sint[k+d2] * cc, radius[0] * _sint[k+d2] * ss, radius[0] * _cost[k+d2],
radius[1] * _sint[k+d2] * cc, radius[1] * _sint[k+d2] * ss, radius[1] * _cost[k+d2],
noInside ? 2 : 0);
creator.calcNormal();
}
}
}
return creator.create();
}
/** @summary Creates tube geometry
* @private */
function createTubeBuffer(shape, faces_limit) {
let outerR, innerR; // inner/outer tube radius
if ((shape._typename === clTGeoCone) || (shape._typename === clTGeoConeSeg)) {
outerR = [shape.fRmax2, shape.fRmax1];
innerR = [shape.fRmin2, shape.fRmin1];
} else {
outerR = [shape.fRmax, shape.fRmax];
innerR = [shape.fRmin, shape.fRmin];
}
const hasrmin = (innerR[0] > 0) || (innerR[1] > 0);
let thetaStart = 0, thetaLength = 360;
if ((shape._typename === clTGeoConeSeg) || (shape._typename === clTGeoTubeSeg) || (shape._typename === clTGeoCtub)) {
thetaStart = shape.fPhi1;
thetaLength = shape.fPhi2 - shape.fPhi1;
}
const radiusSegments = Math.max(4, Math.round(thetaLength / _cfg.GradPerSegm));
// external surface
let numfaces = radiusSegments * (((outerR[0] <= 0) || (outerR[1] <= 0)) ? 1 : 2);
// internal surface
if (hasrmin)
numfaces += radiusSegments * (((innerR[0] <= 0) || (innerR[1] <= 0)) ? 1 : 2);
// upper cap
if (outerR[0] > 0) numfaces += radiusSegments * ((innerR[0] > 0) ? 2 : 1);
// bottom cup
if (outerR[1] > 0) numfaces += radiusSegments * ((innerR[1] > 0) ? 2 : 1);
if (thetaLength < 360)
numfaces += ((outerR[0] > innerR[0]) ? 2 : 0) + ((outerR[1] > innerR[1]) ? 2 : 0);
if (faces_limit < 0) return numfaces;
const phi0 = thetaStart*Math.PI/180,
dphi = thetaLength/radiusSegments*Math.PI/180,
_sin = new Float32Array(radiusSegments+1),
_cos = new Float32Array(radiusSegments+1);
for (let seg = 0; seg <= radiusSegments; ++seg) {
_cos[seg] = Math.cos(phi0+seg*dphi);
_sin[seg] = Math.sin(phi0+seg*dphi);
}
const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces),
calcZ = (shape._typename !== clTGeoCtub)
? null
: (x, y, z) => {
const arr = (z < 0) ? shape.fNlow : shape.fNhigh;
return ((z < 0) ? -shape.fDz : shape.fDz) - (x*arr[0] + y*arr[1]) / arr[2];
};
// create outer/inner tube
for (let side = 0; side < 2; ++side) {
if ((side === 1) && !hasrmin) break;
const R = (side === 0) ? outerR : innerR, d1 = side, d2 = 1 - side;
let nxy = 1, nz = 0;
if (R[0] !== R[1]) {
const angle = Math.atan2((R[1]-R[0]), 2*shape.fDZ);
nxy = Math.cos(angle);
nz = Math.sin(angle);
}
if (side === 1) { nxy *= -1; nz *= -1; }
const reduce = (R[0] <= 0) ? 2 : ((R[1] <= 0) ? 1 : 0);
for (let seg = 0; seg < radiusSegments; ++seg) {
creator.addFace4(
R[0] * _cos[seg+d1], R[0] * _sin[seg+d1], shape.fDZ,
R[1] * _cos[seg+d1], R[1] * _sin[seg+d1], -shape.fDZ,
R[1] * _cos[seg+d2], R[1] * _sin[seg+d2], -shape.fDZ,
R[0] * _cos[seg+d2], R[0] * _sin[seg+d2], shape.fDZ,
reduce);
if (calcZ) creator.recalcZ(calcZ);
creator.setNormal_12_34(nxy*_cos[seg+d1], nxy*_sin[seg+d1], nz,
nxy*_cos[seg+d2], nxy*_sin[seg+d2], nz,
reduce);
}
}
// create upper/bottom part
for (let side = 0; side < 2; ++side) {
if (outerR[side] <= 0) continue;
const d1 = side, d2 = 1- side,
sign = (side === 0) ? 1 : -1,
reduce = (innerR[side] <= 0) ? 2 : 0;
if ((reduce === 2) && (thetaLength === 360) && !calcZ)
creator.startPolygon(side === 0);
for (let seg = 0; seg < radiusSegments; ++seg) {
creator.addFace4(
innerR[side] * _cos[seg+d1], innerR[side] * _sin[seg+d1], sign*shape.fDZ,
outerR[side] * _cos[seg+d1], outerR[side] * _sin[seg+d1], sign*shape.fDZ,
outerR[side] * _cos[seg+d2], outerR[side] * _sin[seg+d2], sign*shape.fDZ,
innerR[side] * _cos[seg+d2], innerR[side] * _sin[seg+d2], sign*shape.fDZ,
reduce);
if (calcZ) {
creator.recalcZ(calcZ);
creator.calcNormal();
} else
creator.setNormal(0, 0, sign);
}
creator.stopPolygon();
}
// create cut surfaces
if (thetaLength < 360) {
creator.addFace4(innerR[1] * _cos[0], innerR[1] * _sin[0], -shape.fDZ,
outerR[1] * _cos[0], outerR[1] * _sin[0], -shape.fDZ,
outerR[0] * _cos[0], outerR[0] * _sin[0], shape.fDZ,
innerR[0] * _cos[0], innerR[0] * _sin[0], shape.fDZ,
(outerR[0] === innerR[0]) ? 2 : ((innerR[1] === outerR[1]) ? 1 : 0));
if (calcZ) creator.recalcZ(calcZ);
creator.calcNormal();
creator.addFace4(innerR[0] * _cos[radiusSegments], innerR[0] * _sin[radiusSegments], shape.fDZ,
outerR[0] * _cos[radiusSegments], outerR[0] * _sin[radiusSegments], shape.fDZ,
outerR[1] * _cos[radiusSegments], outerR[1] * _sin[radiusSegments], -shape.fDZ,
innerR[1] * _cos[radiusSegments], innerR[1] * _sin[radiusSegments], -shape.fDZ,
(outerR[0] === innerR[0]) ? 1 : ((innerR[1] === outerR[1]) ? 2 : 0));
if (calcZ) creator.recalcZ(calcZ);
creator.calcNormal();
}
return creator.create();
}
/** @summary Creates eltu geometry
* @private */
function createEltuBuffer(shape, faces_limit) {
const radiusSegments = Math.max(4, Math.round(360 / _cfg.GradPerSegm));
if (faces_limit < 0) return radiusSegments*4;
// calculate all sin/cos tables in advance
const x = new Float32Array(radiusSegments+1),
y = new Float32Array(radiusSegments+1);
for (let seg=0; seg<=radiusSegments; ++seg) {
const phi = seg/radiusSegments*2*Math.PI;
x[seg] = shape.fRmin*Math.cos(phi);
y[seg] = shape.fRmax*Math.sin(phi);
}
const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(radiusSegments*4);
let nx1, ny1, nx2 = 1, ny2 = 0;
// create tube faces
for (let seg = 0; seg < radiusSegments; ++seg) {
creator.addFace4(x[seg], y[seg], shape.fDZ,
x[seg], y[seg], -shape.fDZ,
x[seg+1], y[seg+1], -shape.fDZ,
x[seg+1], y[seg+1], shape.fDZ);
// calculate normals ourself
nx1 = nx2; ny1 = ny2;
nx2 = x[seg+1] * shape.fRmax / shape.fRmin;
ny2 = y[seg+1] * shape.fRmin / shape.fRmax;
const dist = Math.sqrt(nx2**2 + ny2**2);
nx2 /= dist;
ny2 /= dist;
creator.setNormal_12_34(nx1, ny1, 0, nx2, ny2, 0);
}
// create top/bottom sides
for (let side = 0; side < 2; ++side) {
const sign = (side === 0) ? 1 : -1, d1 = side, d2 = 1 - side;
for (let seg=0; seg<radiusSegments; ++seg) {
creator.addFace3(0, 0, sign*shape.fDZ,
x[seg+d1], y[seg+d1], sign*shape.fDZ,
x[seg+d2], y[seg+d2], sign*shape.fDZ);
creator.setNormal(0, 0, sign);
}
}
return creator.create();
}
/** @summary Creates torus geometry
* @private */
function createTorusBuffer(shape, faces_limit) {
const radius = shape.fR;
let radialSegments = Math.max(6, Math.round(360 / _cfg.GradPerSegm)),
tubularSegments = Math.max(8, Math.round(shape.fDphi / _cfg.GradPerSegm)),
numfaces = (shape.fRmin > 0 ? 4 : 2) * radialSegments * (tubularSegments + (shape.fDphi !== 360 ? 1 : 0));
if (faces_limit < 0) return numfaces;
if ((faces_limit > 0) && (numfaces > faces_limit)) {
radialSegments = Math.floor(radialSegments/Math.sqrt(numfaces / faces_limit));
tubularSegments = Math.floor(tubularSegments/Math.sqrt(numfaces / faces_limit));
numfaces = (shape.fRmin > 0 ? 4 : 2) * radialSegments * (tubularSegments + (shape.fDphi !== 360 ? 1 : 0));
}
const _sinr = new Float32Array(radialSegments+1),
_cosr = new Float32Array(radialSegments+1),
_sint = new Float32Array(tubularSegments+1),
_cost = new Float32Array(tubularSegments+1);
for (let n = 0; n <= radialSegments; ++n) {
_sinr[n] = Math.sin(n/radialSegments*2*Math.PI);
_cosr[n] = Math.cos(n/radialSegments*2*Math.PI);
}
for (let t = 0; t <= tubularSegments; ++t) {
const angle = (shape.fPhi1 + shape.fDphi*t/tubularSegments)/180*Math.PI;
_sint[t] = Math.sin(angle);
_cost[t] = Math.cos(angle);
}
const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces),
// use vectors for normals calculation
p1 = new THREE.Vector3(), p2 = new THREE.Vector3(), p3 = new THREE.Vector3(), p4 = new THREE.Vector3(),
n1 = new THREE.Vector3(), n2 = new THREE.Vector3(), n3 = new THREE.Vector3(), n4 = new THREE.Vector3(),
center1 = new THREE.Vector3(), center2 = new THREE.Vector3();
for (let side = 0; side < 2; ++side) {
if ((side > 0) && (shape.fRmin <= 0)) break;
const tube = (side > 0) ? shape.fRmin : shape.fRmax,
d1 = 1 - side, d2 = 1 - d1, ns = side > 0 ? -1 : 1;
for (let t = 0; t < tubularSegments; ++t) {
const t1 = t + d1, t2 = t + d2;
center1.x = radius * _cost[t1]; center1.y = radius * _sint[t1];
center2.x = radius * _cost[t2]; center2.y = radius * _sint[t2];
for (let n = 0; n < radialSegments; ++n) {
p1.x = (radius + tube * _cosr[n]) * _cost[t1]; p1.y = (radius + tube * _cosr[n]) * _sint[t1]; p1.z = tube*_sinr[n];
p2.x = (radius + tube * _cosr[n+1]) * _cost[t1]; p2.y = (radius + tube * _cosr[n+1]) * _sint[t1]; p2.z = tube*_sinr[n+1];
p3.x = (radius + tube * _cosr[n+1]) * _cost[t2]; p3.y = (radius + tube * _cosr[n+1]) * _sint[t2]; p3.z = tube*_sinr[n+1];
p4.x = (radius + tube * _cosr[n]) * _cost[t2]; p4.y = (radius + tube * _cosr[n]) * _sint[t2]; p4.z = tube*_sinr[n];
creator.addFace4(p1.x, p1.y, p1.z,
p2.x, p2.y, p2.z,
p3.x, p3.y, p3.z,
p4.x, p4.y, p4.z);
n1.subVectors(p1, center1).normalize();
n2.subVectors(p2, center1).normalize();
n3.subVectors(p3, center2).normalize();
n4.subVectors(p4, center2).normalize();
creator.setNormal4(ns*n1.x, ns*n1.y, ns*n1.z,
ns*n2.x, ns*n2.y, ns*n2.z,
ns*n3.x, ns*n3.y, ns*n3.z,
ns*n4.x, ns*n4.y, ns*n4.z);
}
}
}
if (shape.fDphi !== 360) {
for (let t = 0; t <= tubularSegments; t += tubularSegments) {
const tube1 = shape.fRmax, tube2 = shape.fRmin,
d1 = t > 0 ? 0 : 1, d2 = 1 - d1,
skip = shape.fRmin > 0 ? 0 : 1,
nsign = t > 0 ? 1 : -1;
for (let n = 0; n < radialSegments; ++n) {
creator.addFace4((radius + tube1 * _cosr[n+d1]) * _cost[t], (radius + tube1 * _cosr[n+d1]) * _sint[t], tube1*_sinr[n+d1],
(radius + tube2 * _cosr[n+d1]) * _cost[t], (radius + tube2 * _cosr[n+d1]) * _sint[t], tube2*_sinr[n+d1],
(radius + tube2 * _cosr[n+d2]) * _cost[t], (radius + tube2 * _cosr[n+d2]) * _sint[t], tube2*_sinr[n+d2],
(radius + tube1 * _cosr[n+d2]) * _cost[t], (radius + tube1 * _cosr[n+d2]) * _sint[t], tube1*_sinr[n+d2], skip);
creator.setNormal(-nsign * _sint[t], nsign * _cost[t], 0);
}
}
}
return creator.create();
}
/** @summary Creates polygon geometry
* @private */
function createPolygonBuffer(shape, faces_limit) {
const thetaStart = shape.fPhi1,
thetaLength = shape.fDphi;
let radiusSegments, factor;
if (shape._typename === clTGeoPgon) {
radiusSegments = shape.fNedges;
factor = 1.0 / Math.cos(Math.PI/180 * thetaLength / radiusSegments / 2);
} else {
radiusSegments = Math.max(5, Math.round(thetaLength / _cfg.GradPerSegm));
factor = 1;
}
const usage = new Int16Array(2*shape.fNz);
let numusedlayers = 0, hasrmin = false;
for (let layer = 0; layer < shape.fNz; ++layer)
hasrmin = hasrmin || (shape.fRmin[layer] > 0);
// return very rough estimation, number of faces may be much less
if (faces_limit < 0)
return (hasrmin ? 4 : 2) * radiusSegments * (shape.fNz-1);
// coordinate of point on cut edge (x,z)
const pnts = (thetaLength === 360) ? null : [];
// first analyze levels - if we need to create all of them
for (let side = 0; side < 2; ++side) {
const rside = (side === 0) ? 'fRmax' : 'fRmin';
for (let layer=0; layer < shape.fNz; ++layer) {
// first create points for the layer
const layerz = shape.fZ[layer], rad = shape[rside][layer];
usage[layer*2+side] = 0;
if ((layer > 0) && (layer < shape.fNz-1)) {
if (((shape.fZ[layer-1] === layerz) && (shape[rside][layer-1] === rad)) ||
((shape[rside][layer+1] === rad) && (shape[rside][layer-1] === rad))) {
// same Z and R as before - ignore
// or same R before and after
continue;
}
}
if ((layer > 0) && ((side === 0) || hasrmin)) {
usage[layer*2+side] = 1;
numusedlayers++;
}
if (pnts !== null) {
if (side === 0)
pnts.push(new THREE.Vector2(factor*rad, layerz));
else if (rad < shape.fRmax[layer])
pnts.unshift(new THREE.Vector2(factor*rad, layerz));
}
}
}
let numfaces = numusedlayers*radiusSegments*2;
if (shape.fRmin[0] !== shape.fRmax[0])
numfaces += radiusSegments * (hasrmin ? 2 : 1);
if (shape.fRmin[shape.fNz-1] !== shape.fRmax[shape.fNz-1])
numfaces += radiusSegments * (hasrmin ? 2 : 1);
let cut_faces = null;
if (pnts !== null) {
if (pnts.length === shape.fNz * 2) {
// special case - all layers are there, create faces ourself
cut_faces = [];
for (let layer = shape.fNz-1; layer > 0; --layer) {
if (shape.fZ[layer] === shape.fZ[layer-1]) continue;
const right = 2*shape.fNz - 1 - layer;
cut_faces.push([right, layer - 1, layer]);
cut_faces.push([right, right + 1, layer-1]);
}
} else {
// let three.js calculate our faces
cut_faces = THREE.ShapeUtils.triangulateShape(pnts, []);
}
numfaces += cut_faces.length*2;
}
const phi0 = thetaStart*Math.PI/180,
dphi = thetaLength/radiusSegments*Math.PI/180,
// calculate all sin/cos tables in advance
_sin = new Float32Array(radiusSegments+1),
_cos = new Float32Array(radiusSegments+1);
for (let seg = 0; seg <= radiusSegments; ++seg) {
_cos[seg] = Math.cos(phi0+seg*dphi);
_sin[seg] = Math.sin(phi0+seg*dphi);
}
const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces);
// add sides
for (let side = 0; side < 2; ++side) {
const rside = (side === 0) ? 'fRmax' : 'fRmin',
d1 = 1 - side, d2 = side;
let z1 = shape.fZ[0], r1 = factor*shape[rside][0];
for (let layer = 0; layer < shape.fNz; ++layer) {
if (usage[layer*2+side] === 0) continue;
const z2 = shape.fZ[layer], r2 = factor*shape[rside][layer];
let nxy = 1, nz = 0;
if ((r2 !== r1)) {
const angle = Math.atan2((r2-r1), (z2-z1));
nxy = Math.cos(angle);
nz = Math.sin(angle);
}
if (side > 0) { nxy*=-1; nz*=-1; }
for (let seg = 0; seg < radiusSegments; ++seg) {
creator.addFace4(r1 * _cos[seg+d1], r1 * _sin[seg+d1], z1,
r2 * _cos[seg+d1], r2 * _sin[seg+d1], z2,
r2 * _cos[seg+d2], r2 * _sin[seg+d2], z2,
r1 * _cos[seg+d2], r1 * _sin[seg+d2], z1);
creator.setNormal_12_34(nxy*_cos[seg+d1], nxy*_sin[seg+d1], nz, nxy*_cos[seg+d2], nxy*_sin[seg+d2], nz);
}
z1 = z2; r1 = r2;
}
}
// add top/bottom
for (let layer = 0; layer < shape.fNz; layer += (shape.fNz-1)) {
const rmin = factor*shape.fRmin[layer], rmax = factor*shape.fRmax[layer];
if (rmin === rmax) continue;
const layerz = shape.fZ[layer],
d1 = (layer === 0) ? 1 : 0, d2 = 1 - d1,
normalz = (layer === 0) ? -1: 1;
if (!hasrmin && !cut_faces)
creator.startPolygon(layer > 0);
for (let seg = 0; seg < radiusSegments; ++seg) {
creator.addFace4(rmin * _cos[seg+d1], rmin * _sin[seg+d1], layerz,
rmax * _cos[seg+d1], rmax * _sin[seg+d1], layerz,
rmax * _cos[seg+d2], rmax * _sin[seg+d2], layerz,
rmin * _cos[seg+d2], rmin * _sin[seg+d2], layerz,
hasrmin ? 0 : 2);
creator.setNormal(0, 0, normalz);
}
creator.stopPolygon();
}
if (cut_faces) {
for (let seg = 0; seg <= radiusSegments; seg += radiusSegments) {
const d1 = (seg === 0) ? 1 : 2, d2 = 3 - d1;
for (let n=0; n<cut_faces.length; ++n) {
const a = pnts[cut_faces[n][0]],
b = pnts[cut_faces[n][d1]],
c = pnts[cut_faces[n][d2]];
creator.addFace3(a.x * _cos[seg], a.x * _sin[seg], a.y,
b.x * _cos[seg], b.x * _sin[seg], b.y,
c.x * _cos[seg], c.x * _sin[seg], c.y);
creator.calcNormal();
}
}
}
return creator.create();
}
/** @summary Creates xtru geometry
* @private */
function createXtruBuffer(shape, faces_limit) {
let nfaces = (shape.fNz-1) * shape.fNvert * 2;
if (faces_limit < 0)
return nfaces + shape.fNvert*3;
// create points
const pnts = [];
for (let vert = 0; vert < shape.fNvert; ++vert)
pnts.push(new THREE.Vector2(shape.fX[vert], shape.fY[vert]));
let faces = THREE.ShapeUtils.triangulateShape(pnts, []);
if (faces.length < pnts.length - 2) {
geoWarn(`Problem with XTRU shape ${shape.fName} with ${pnts.length} vertices`);
faces = [];
} else
nfaces += faces.length * 2;
const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(nfaces);
for (let layer = 0; layer < shape.fNz-1; ++layer) {
const z1 = shape.fZ[layer], scale1 = shape.fScale[layer],
z2 = shape.fZ[layer+1], scale2 = shape.fScale[layer+1],
x01 = shape.fX0[layer], x02 = shape.fX0[layer+1],
y01 = shape.fY0[layer], y02 = shape.fY0[layer+1];
for (let vert1 = 0; vert1 < shape.fNvert; ++vert1) {
const vert2 = (vert1+1) % shape.fNvert;
creator.addFace4(scale1 * shape.fX[vert1] + x01, scale1 * shape.fY[vert1] + y01, z1,
scale2 * shape.fX[vert1] + x02, scale2 * shape.fY[vert1] + y02, z2,
scale2 * shape.fX[vert2] + x02, scale2 * shape.fY[vert2] + y02, z2,
scale1 * shape.fX[vert2] + x01, scale1 * shape.fY[vert2] + y01, z1);
creator.calcNormal();
}
}
for (let layer = 0; layer <= shape.fNz-1; layer += (shape.fNz-1)) {
const z = shape.fZ[layer], scale = shape.fScale[layer],
x0 = shape.fX0[layer], y0 = shape.fY0[layer];
for (let n = 0; n < faces.length; ++n) {
const face = faces[n],
pnt1 = pnts[face[0]],
pnt2 = pnts[face[layer === 0 ? 2 : 1]],
pnt3 = pnts[face[layer === 0 ? 1 : 2]];
creator.addFace3(scale * pnt1.x + x0, scale * pnt1.y + y0, z,
scale * pnt2.x + x0, scale * pnt2.y + y0, z,
scale * pnt3.x + x0, scale * pnt3.y + y0, z);
creator.setNormal(0, 0, layer === 0 ? -1 : 1);
}
}
return creator.create();
}
/** @summary Creates para geometry
* @private */
function createParaboloidBuffer(shape, faces_limit) {
let radiusSegments = Math.max(4, Math.round(360 / _cfg.GradPerSegm)),
heightSegments = 30;
if (faces_limit > 0) {
const fact = 2*radiusSegments*(heightSegments+1) / faces_limit;
if (fact > 1.0) {
radiusSegments = Math.max(5, Math.floor(radiusSegments/Math.sqrt(fact)));
heightSegments = Math.max(5, Math.floor(heightSegments/Math.sqrt(fact)));
}
}
const rmin = shape.fRlo, rmax = sh