UNPKG

jsroot

Version:
1,414 lines (1,171 loc) 140 kB
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