geom/geobase.mjs

  1. import { isObject, isFunc, BIT } from '../core.mjs';
  2. import { THREE } from '../base/base3d.mjs';
  3. import { createBufferGeometry, createNormal,
  4. Vertex as CsgVertex, Geometry as CsgGeometry, Polygon as CsgPolygon } from './csg.mjs';
  5. const _cfg = {
  6. GradPerSegm: 6, // grad per segment in cylinder/spherical symmetry shapes
  7. CompressComp: true // use faces compression in composite shapes
  8. };
  9. /** @summary Returns or set geometry config values
  10. * @desc Supported 'GradPerSegm' and 'CompressComp'
  11. * @private */
  12. function geoCfg(name, value) {
  13. if (value === undefined)
  14. return _cfg[name];
  15. _cfg[name] = value;
  16. }
  17. const kindGeo = 0, // TGeoNode / TGeoShape
  18. kindEve = 1, // TEveShape / TEveGeoShapeExtract
  19. kindShape = 2, // special kind for single shape handling
  20. /** @summary TGeo-related bits
  21. * @private */
  22. geoBITS = {
  23. kVisOverride: BIT(0), // volume's vis. attributes are overwritten
  24. kVisNone: BIT(1), // the volume/node is invisible, as well as daughters
  25. kVisThis: BIT(2), // this volume/node is visible
  26. kVisDaughters: BIT(3), // all leaves are visible
  27. kVisOneLevel: BIT(4), // first level daughters are visible (not used)
  28. kVisStreamed: BIT(5), // true if attributes have been streamed
  29. kVisTouched: BIT(6), // true if attributes are changed after closing geom
  30. kVisOnScreen: BIT(7), // true if volume is visible on screen
  31. kVisContainers: BIT(12), // all containers visible
  32. kVisOnly: BIT(13), // just this visible
  33. kVisBranch: BIT(14), // only a given branch visible
  34. kVisRaytrace: BIT(15) // raytracing flag
  35. },
  36. clTGeoBBox = 'TGeoBBox',
  37. clTGeoArb8 = 'TGeoArb8',
  38. clTGeoCone = 'TGeoCone',
  39. clTGeoConeSeg = 'TGeoConeSeg',
  40. clTGeoTube = 'TGeoTube',
  41. clTGeoTubeSeg = 'TGeoTubeSeg',
  42. clTGeoCtub = 'TGeoCtub',
  43. clTGeoTrd1 = 'TGeoTrd1',
  44. clTGeoTrd2 = 'TGeoTrd2',
  45. clTGeoPara = 'TGeoPara',
  46. clTGeoParaboloid = 'TGeoParaboloid',
  47. clTGeoPcon = 'TGeoPcon',
  48. clTGeoPgon = 'TGeoPgon',
  49. clTGeoShapeAssembly = 'TGeoShapeAssembly',
  50. clTGeoSphere = 'TGeoSphere',
  51. clTGeoTorus = 'TGeoTorus',
  52. clTGeoXtru = 'TGeoXtru',
  53. clTGeoTrap = 'TGeoTrap',
  54. clTGeoGtra = 'TGeoGtra',
  55. clTGeoEltu = 'TGeoEltu',
  56. clTGeoHype = 'TGeoHype',
  57. clTGeoCompositeShape = 'TGeoCompositeShape',
  58. clTGeoHalfSpace = 'TGeoHalfSpace',
  59. clTGeoScaledShape = 'TGeoScaledShape';
  60. /** @summary Test fGeoAtt bits
  61. * @private */
  62. function testGeoBit(volume, f) {
  63. const att = volume.fGeoAtt;
  64. return att === undefined ? false : ((att & f) !== 0);
  65. }
  66. /** @summary Set fGeoAtt bit
  67. * @private */
  68. function setGeoBit(volume, f, value) {
  69. if (volume.fGeoAtt === undefined) return;
  70. volume.fGeoAtt = value ? (volume.fGeoAtt | f) : (volume.fGeoAtt & ~f);
  71. }
  72. /** @summary Toggle fGeoAttBit
  73. * @private */
  74. function toggleGeoBit(volume, f) {
  75. if (volume.fGeoAtt !== undefined)
  76. volume.fGeoAtt ^= f & 0xffffff;
  77. }
  78. /** @summary Implementation of TGeoVolume::InvisibleAll
  79. * @private */
  80. function setInvisibleAll(volume, flag) {
  81. if (flag === undefined) flag = true;
  82. setGeoBit(volume, geoBITS.kVisThis, !flag);
  83. // setGeoBit(this, geoBITS.kVisDaughters, !flag);
  84. if (volume.fNodes) {
  85. for (let n = 0; n < volume.fNodes.arr.length; ++n) {
  86. const sub = volume.fNodes.arr[n].fVolume;
  87. setGeoBit(sub, geoBITS.kVisThis, !flag);
  88. // setGeoBit(sub, geoBITS.kVisDaughters, !flag);
  89. }
  90. }
  91. }
  92. const _warn_msgs = {};
  93. /** @summary method used to avoid duplication of warnings
  94. * @private */
  95. function geoWarn(msg) {
  96. if (_warn_msgs[msg] !== undefined) return;
  97. _warn_msgs[msg] = true;
  98. console.warn(msg);
  99. }
  100. /** @summary Analyze TGeo node kind
  101. * @desc 0 - TGeoNode
  102. * 1 - TEveGeoNode
  103. * -1 - unsupported
  104. * @return detected node kind
  105. * @private */
  106. function getNodeKind(obj) {
  107. if (!isObject(obj)) return -1;
  108. return ('fShape' in obj) && ('fTrans' in obj) ? kindEve : kindGeo;
  109. }
  110. /** @summary Returns number of shapes
  111. * @desc Used to count total shapes number in composites
  112. * @private */
  113. function countNumShapes(shape) {
  114. if (!shape) return 0;
  115. if (shape._typename !== clTGeoCompositeShape) return 1;
  116. return countNumShapes(shape.fNode.fLeft) + countNumShapes(shape.fNode.fRight);
  117. }
  118. /** @summary Returns geo object name
  119. * @desc Can appends some special suffixes
  120. * @private */
  121. function getObjectName(obj) {
  122. return obj?.fName ? (obj.fName + (obj.$geo_suffix || '')) : '';
  123. }
  124. /** @summary Check duplicates
  125. * @private */
  126. function checkDuplicates(parent, chlds) {
  127. if (parent) {
  128. if (parent.$geo_checked) return;
  129. parent.$geo_checked = true;
  130. }
  131. const names = [], cnts = [];
  132. for (let k = 0; k < chlds.length; ++k) {
  133. const chld = chlds[k];
  134. if (!chld?.fName) continue;
  135. if (!chld.$geo_suffix) {
  136. const indx = names.indexOf(chld.fName);
  137. if (indx >= 0) {
  138. let cnt = cnts[indx] || 1;
  139. while (names.indexOf(chld.fName+'#'+cnt) >= 0) ++cnt;
  140. chld.$geo_suffix = '#' + cnt;
  141. cnts[indx] = cnt+1;
  142. }
  143. }
  144. names.push(getObjectName(chld));
  145. }
  146. }
  147. /** @summary Create normal to plane, defined with three points
  148. * @private */
  149. function produceNormal(x1, y1, z1, x2, y2, z2, x3, y3, z3) {
  150. const pA = new THREE.Vector3(x1, y1, z1),
  151. pB = new THREE.Vector3(x2, y2, z2),
  152. pC = new THREE.Vector3(x3, y3, z3),
  153. cb = new THREE.Vector3(),
  154. ab = new THREE.Vector3();
  155. cb.subVectors(pC, pB);
  156. ab.subVectors(pA, pB);
  157. cb.cross(ab);
  158. return cb;
  159. }
  160. // ==========================================================================
  161. /**
  162. * @summary Helper class for geometry creation
  163. *
  164. * @private
  165. */
  166. class GeometryCreator {
  167. /** @summary Constructor
  168. * @param numfaces - number of faces */
  169. constructor(numfaces) {
  170. this.nfaces = numfaces;
  171. this.indx = 0;
  172. this.pos = new Float32Array(numfaces*9);
  173. this.norm = new Float32Array(numfaces*9);
  174. }
  175. /** @summary Add face with 3 vertices */
  176. addFace3(x1, y1, z1, x2, y2, z2, x3, y3, z3) {
  177. const indx = this.indx, pos = this.pos;
  178. pos[indx] = x1;
  179. pos[indx+1] = y1;
  180. pos[indx+2] = z1;
  181. pos[indx+3] = x2;
  182. pos[indx+4] = y2;
  183. pos[indx+5] = z2;
  184. pos[indx+6] = x3;
  185. pos[indx+7] = y3;
  186. pos[indx+8] = z3;
  187. this.last4 = false;
  188. this.indx = indx + 9;
  189. }
  190. /** @summary Start polygon */
  191. startPolygon() {}
  192. /** @summary Stop polygon */
  193. stopPolygon() {}
  194. /** @summary Add face with 4 vertices
  195. * @desc From four vertices one normally creates two faces (1,2,3) and (1,3,4)
  196. * if (reduce === 1), first face is reduced
  197. * if (reduce === 2), second face is reduced */
  198. addFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, reduce) {
  199. let indx = this.indx;
  200. const pos = this.pos;
  201. if (reduce !== 1) {
  202. pos[indx] = x1;
  203. pos[indx+1] = y1;
  204. pos[indx+2] = z1;
  205. pos[indx+3] = x2;
  206. pos[indx+4] = y2;
  207. pos[indx+5] = z2;
  208. pos[indx+6] = x3;
  209. pos[indx+7] = y3;
  210. pos[indx+8] = z3;
  211. indx+=9;
  212. }
  213. if (reduce !== 2) {
  214. pos[indx] = x1;
  215. pos[indx+1] = y1;
  216. pos[indx+2] = z1;
  217. pos[indx+3] = x3;
  218. pos[indx+4] = y3;
  219. pos[indx+5] = z3;
  220. pos[indx+6] = x4;
  221. pos[indx+7] = y4;
  222. pos[indx+8] = z4;
  223. indx+=9;
  224. }
  225. this.last4 = (indx !== this.indx + 9);
  226. this.indx = indx;
  227. }
  228. /** @summary Specify normal for face with 4 vertices
  229. * @desc same as addFace4, assign normals for each individual vertex
  230. * reduce has same meaning and should be the same */
  231. setNormal4(nx1, ny1, nz1, nx2, ny2, nz2, nx3, ny3, nz3, nx4, ny4, nz4, reduce) {
  232. if (this.last4 && reduce)
  233. return console.error('missmatch between addFace4 and setNormal4 calls');
  234. let indx = this.indx - (this.last4 ? 18 : 9);
  235. const norm = this.norm;
  236. if (reduce !== 1) {
  237. norm[indx] = nx1;
  238. norm[indx+1] = ny1;
  239. norm[indx+2] = nz1;
  240. norm[indx+3] = nx2;
  241. norm[indx+4] = ny2;
  242. norm[indx+5] = nz2;
  243. norm[indx+6] = nx3;
  244. norm[indx+7] = ny3;
  245. norm[indx+8] = nz3;
  246. indx+=9;
  247. }
  248. if (reduce !== 2) {
  249. norm[indx] = nx1;
  250. norm[indx+1] = ny1;
  251. norm[indx+2] = nz1;
  252. norm[indx+3] = nx3;
  253. norm[indx+4] = ny3;
  254. norm[indx+5] = nz3;
  255. norm[indx+6] = nx4;
  256. norm[indx+7] = ny4;
  257. norm[indx+8] = nz4;
  258. }
  259. }
  260. /** @summary Recalculate Z with provided func */
  261. recalcZ(func) {
  262. const pos = this.pos,
  263. last = this.indx;
  264. let indx = last - (this.last4 ? 18 : 9);
  265. while (indx < last) {
  266. pos[indx+2] = func(pos[indx], pos[indx+1], pos[indx+2]);
  267. indx+=3;
  268. }
  269. }
  270. /** @summary Calculate normal */
  271. calcNormal() {
  272. if (!this.cb) {
  273. this.pA = new THREE.Vector3();
  274. this.pB = new THREE.Vector3();
  275. this.pC = new THREE.Vector3();
  276. this.cb = new THREE.Vector3();
  277. this.ab = new THREE.Vector3();
  278. }
  279. this.pA.fromArray(this.pos, this.indx - 9);
  280. this.pB.fromArray(this.pos, this.indx - 6);
  281. this.pC.fromArray(this.pos, this.indx - 3);
  282. this.cb.subVectors(this.pC, this.pB);
  283. this.ab.subVectors(this.pA, this.pB);
  284. this.cb.cross(this.ab);
  285. this.setNormal(this.cb.x, this.cb.y, this.cb.z);
  286. }
  287. /** @summary Set normal */
  288. setNormal(nx, ny, nz) {
  289. let indx = this.indx - 9;
  290. const norm = this.norm;
  291. norm[indx] = norm[indx+3] = norm[indx+6] = nx;
  292. norm[indx+1] = norm[indx+4] = norm[indx+7] = ny;
  293. norm[indx+2] = norm[indx+5] = norm[indx+8] = nz;
  294. if (this.last4) {
  295. indx -= 9;
  296. norm[indx] = norm[indx+3] = norm[indx+6] = nx;
  297. norm[indx+1] = norm[indx+4] = norm[indx+7] = ny;
  298. norm[indx+2] = norm[indx+5] = norm[indx+8] = nz;
  299. }
  300. }
  301. /** @summary Set normal
  302. * @desc special shortcut, when same normals can be applied for 1-2 point and 3-4 point */
  303. setNormal_12_34(nx12, ny12, nz12, nx34, ny34, nz34, reduce) {
  304. if (reduce === undefined) reduce = 0;
  305. let indx = this.indx - ((reduce > 0) ? 9 : 18);
  306. const norm = this.norm;
  307. if (reduce !== 1) {
  308. norm[indx] = nx12;
  309. norm[indx+1] = ny12;
  310. norm[indx+2] = nz12;
  311. norm[indx+3] = nx12;
  312. norm[indx+4] = ny12;
  313. norm[indx+5] = nz12;
  314. norm[indx+6] = nx34;
  315. norm[indx+7] = ny34;
  316. norm[indx+8] = nz34;
  317. indx += 9;
  318. }
  319. if (reduce !== 2) {
  320. norm[indx] = nx12;
  321. norm[indx+1] = ny12;
  322. norm[indx+2] = nz12;
  323. norm[indx+3] = nx34;
  324. norm[indx+4] = ny34;
  325. norm[indx+5] = nz34;
  326. norm[indx+6] = nx34;
  327. norm[indx+7] = ny34;
  328. norm[indx+8] = nz34;
  329. }
  330. }
  331. /** @summary Create geometry */
  332. create() {
  333. if (this.nfaces !== this.indx/9)
  334. console.error(`Mismatch with created ${this.nfaces} and filled ${this.indx/9} number of faces`);
  335. const geometry = new THREE.BufferGeometry();
  336. geometry.setAttribute('position', new THREE.BufferAttribute(this.pos, 3));
  337. geometry.setAttribute('normal', new THREE.BufferAttribute(this.norm, 3));
  338. return geometry;
  339. }
  340. }
  341. // ================================================================================
  342. /** @summary Helper class for CsgGeometry creation
  343. *
  344. * @private
  345. */
  346. class PolygonsCreator {
  347. /** @summary constructor */
  348. constructor() {
  349. this.polygons = [];
  350. }
  351. /** @summary Start polygon */
  352. startPolygon(normal) {
  353. this.multi = 1;
  354. this.mnormal = normal;
  355. }
  356. /** @summary Stop polygon */
  357. stopPolygon() {
  358. if (!this.multi) return;
  359. this.multi = 0;
  360. console.error('Polygon should be already closed at this moment');
  361. }
  362. /** @summary Add face with 3 vertices */
  363. addFace3(x1, y1, z1, x2, y2, z2, x3, y3, z3) {
  364. this.addFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x3, y3, z3, 2);
  365. }
  366. /** @summary Add face with 4 vertices
  367. * @desc From four vertices one normally creates two faces (1,2,3) and (1,3,4)
  368. * if (reduce === 1), first face is reduced
  369. * if (reduce === 2), second face is reduced */
  370. addFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, reduce) {
  371. if (reduce === undefined) reduce = 0;
  372. this.v1 = new CsgVertex(x1, y1, z1, 0, 0, 0);
  373. this.v2 = (reduce === 1) ? null : new CsgVertex(x2, y2, z2, 0, 0, 0);
  374. this.v3 = new CsgVertex(x3, y3, z3, 0, 0, 0);
  375. this.v4 = (reduce === 2) ? null : new CsgVertex(x4, y4, z4, 0, 0, 0);
  376. this.reduce = reduce;
  377. if (this.multi) {
  378. if (reduce !== 2)
  379. console.error('polygon not supported for not-reduced faces');
  380. let polygon;
  381. if (this.multi++ === 1) {
  382. polygon = new CsgPolygon();
  383. polygon.vertices.push(this.mnormal ? this.v2 : this.v3);
  384. this.polygons.push(polygon);
  385. } else {
  386. polygon = this.polygons.at(-1);
  387. // check that last vertex equals to v2
  388. const last = this.mnormal ? polygon.vertices.at(-1) : polygon.vertices.at(0),
  389. comp = this.mnormal ? this.v2 : this.v3;
  390. if (comp.diff(last) > 1e-12)
  391. console.error('vertex missmatch when building polygon');
  392. }
  393. const first = this.mnormal ? polygon.vertices[0] : polygon.vertices.at(-1),
  394. next = this.mnormal ? this.v3 : this.v2;
  395. if (next.diff(first) < 1e-12)
  396. this.multi = 0;
  397. else if (this.mnormal)
  398. polygon.vertices.push(this.v3);
  399. else
  400. polygon.vertices.unshift(this.v2);
  401. return;
  402. }
  403. const polygon = new CsgPolygon();
  404. switch (reduce) {
  405. case 0: polygon.vertices.push(this.v1, this.v2, this.v3, this.v4); break;
  406. case 1: polygon.vertices.push(this.v1, this.v3, this.v4); break;
  407. case 2: polygon.vertices.push(this.v1, this.v2, this.v3); break;
  408. }
  409. this.polygons.push(polygon);
  410. }
  411. /** @summary Specify normal for face with 4 vertices
  412. * @desc same as addFace4, assign normals for each individual vertex
  413. * reduce has same meaning and should be the same */
  414. setNormal4(nx1, ny1, nz1, nx2, ny2, nz2, nx3, ny3, nz3, nx4, ny4, nz4) {
  415. this.v1.setnormal(nx1, ny1, nz1);
  416. if (this.v2) this.v2.setnormal(nx2, ny2, nz2);
  417. this.v3.setnormal(nx3, ny3, nz3);
  418. if (this.v4) this.v4.setnormal(nx4, ny4, nz4);
  419. }
  420. /** @summary Set normal
  421. * @desc special shortcut, when same normals can be applied for 1-2 point and 3-4 point */
  422. setNormal_12_34(nx12, ny12, nz12, nx34, ny34, nz34) {
  423. this.v1.setnormal(nx12, ny12, nz12);
  424. if (this.v2) this.v2.setnormal(nx12, ny12, nz12);
  425. this.v3.setnormal(nx34, ny34, nz34);
  426. if (this.v4) this.v4.setnormal(nx34, ny34, nz34);
  427. }
  428. /** @summary Calculate normal */
  429. calcNormal() {
  430. if (!this.cb) {
  431. this.pA = new THREE.Vector3();
  432. this.pB = new THREE.Vector3();
  433. this.pC = new THREE.Vector3();
  434. this.cb = new THREE.Vector3();
  435. this.ab = new THREE.Vector3();
  436. }
  437. this.pA.set(this.v1.x, this.v1.y, this.v1.z);
  438. if (this.reduce !== 1) {
  439. this.pB.set(this.v2.x, this.v2.y, this.v2.z);
  440. this.pC.set(this.v3.x, this.v3.y, this.v3.z);
  441. } else {
  442. this.pB.set(this.v3.x, this.v3.y, this.v3.z);
  443. this.pC.set(this.v4.x, this.v4.y, this.v4.z);
  444. }
  445. this.cb.subVectors(this.pC, this.pB);
  446. this.ab.subVectors(this.pA, this.pB);
  447. this.cb.cross(this.ab);
  448. this.setNormal(this.cb.x, this.cb.y, this.cb.z);
  449. }
  450. /** @summary Set normal */
  451. setNormal(nx, ny, nz) {
  452. this.v1.setnormal(nx, ny, nz);
  453. if (this.v2) this.v2.setnormal(nx, ny, nz);
  454. this.v3.setnormal(nx, ny, nz);
  455. if (this.v4) this.v4.setnormal(nx, ny, nz);
  456. }
  457. /** @summary Recalculate Z with provided func */
  458. recalcZ(func) {
  459. this.v1.z = func(this.v1.x, this.v1.y, this.v1.z);
  460. if (this.v2) this.v2.z = func(this.v2.x, this.v2.y, this.v2.z);
  461. this.v3.z = func(this.v3.x, this.v3.y, this.v3.z);
  462. if (this.v4) this.v4.z = func(this.v4.x, this.v4.y, this.v4.z);
  463. }
  464. /** @summary Create geometry
  465. * @private */
  466. create() {
  467. return { polygons: this.polygons };
  468. }
  469. }
  470. // ================= all functions to create geometry ===================================
  471. /** @summary Creates cube geometry
  472. * @private */
  473. function createCubeBuffer(shape, faces_limit) {
  474. if (faces_limit < 0) return 12;
  475. const dx = shape.fDX, dy = shape.fDY, dz = shape.fDZ,
  476. creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(12);
  477. creator.addFace4(dx, dy, dz, dx, -dy, dz, dx, -dy, -dz, dx, dy, -dz); creator.setNormal(1, 0, 0);
  478. creator.addFace4(-dx, dy, -dz, -dx, -dy, -dz, -dx, -dy, dz, -dx, dy, dz); creator.setNormal(-1, 0, 0);
  479. creator.addFace4(-dx, dy, -dz, -dx, dy, dz, dx, dy, dz, dx, dy, -dz); creator.setNormal(0, 1, 0);
  480. creator.addFace4(-dx, -dy, dz, -dx, -dy, -dz, dx, -dy, -dz, dx, -dy, dz); creator.setNormal(0, -1, 0);
  481. creator.addFace4(-dx, dy, dz, -dx, -dy, dz, dx, -dy, dz, dx, dy, dz); creator.setNormal(0, 0, 1);
  482. creator.addFace4(dx, dy, -dz, dx, -dy, -dz, -dx, -dy, -dz, -dx, dy, -dz); creator.setNormal(0, 0, -1);
  483. return creator.create();
  484. }
  485. /** @summary Creates 8 edges geometry
  486. * @private */
  487. function create8edgesBuffer(v, faces_limit) {
  488. 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],
  489. creator = (faces_limit > 0) ? new PolygonsCreator() : new GeometryCreator(12);
  490. for (let n = 0; n < indicies.length; n += 4) {
  491. const i1 = indicies[n]*3,
  492. i2 = indicies[n+1]*3,
  493. i3 = indicies[n+2]*3,
  494. i4 = indicies[n+3]*3;
  495. creator.addFace4(v[i1], v[i1+1], v[i1+2], v[i2], v[i2+1], v[i2+2],
  496. v[i3], v[i3+1], v[i3+2], v[i4], v[i4+1], v[i4+2]);
  497. if (n === 0)
  498. creator.setNormal(0, 0, 1);
  499. else if (n === 20)
  500. creator.setNormal(0, 0, -1);
  501. else
  502. creator.calcNormal();
  503. }
  504. return creator.create();
  505. }
  506. /** @summary Creates PARA geometry
  507. * @private */
  508. function createParaBuffer(shape, faces_limit) {
  509. if (faces_limit < 0) return 12;
  510. const txy = shape.fTxy, txz = shape.fTxz, tyz = shape.fTyz, v = [
  511. -shape.fZ*txz-txy*shape.fY-shape.fX, -shape.fY-shape.fZ*tyz, -shape.fZ,
  512. -shape.fZ*txz+txy*shape.fY-shape.fX, shape.fY-shape.fZ*tyz, -shape.fZ,
  513. -shape.fZ*txz+txy*shape.fY+shape.fX, shape.fY-shape.fZ*tyz, -shape.fZ,
  514. -shape.fZ*txz-txy*shape.fY+shape.fX, -shape.fY-shape.fZ*tyz, -shape.fZ,
  515. shape.fZ*txz-txy*shape.fY-shape.fX, -shape.fY+shape.fZ*tyz, shape.fZ,
  516. shape.fZ*txz+txy*shape.fY-shape.fX, shape.fY+shape.fZ*tyz, shape.fZ,
  517. shape.fZ*txz+txy*shape.fY+shape.fX, shape.fY+shape.fZ*tyz, shape.fZ,
  518. shape.fZ*txz-txy*shape.fY+shape.fX, -shape.fY+shape.fZ*tyz, shape.fZ];
  519. return create8edgesBuffer(v, faces_limit);
  520. }
  521. /** @summary Creates trapezoid geometry
  522. * @private */
  523. function createTrapezoidBuffer(shape, faces_limit) {
  524. if (faces_limit < 0) return 12;
  525. let y1, y2;
  526. if (shape._typename === clTGeoTrd1)
  527. y1 = y2 = shape.fDY;
  528. else {
  529. y1 = shape.fDy1; y2 = shape.fDy2;
  530. }
  531. const v = [
  532. -shape.fDx1, y1, -shape.fDZ,
  533. shape.fDx1, y1, -shape.fDZ,
  534. shape.fDx1, -y1, -shape.fDZ,
  535. -shape.fDx1, -y1, -shape.fDZ,
  536. -shape.fDx2, y2, shape.fDZ,
  537. shape.fDx2, y2, shape.fDZ,
  538. shape.fDx2, -y2, shape.fDZ,
  539. -shape.fDx2, -y2, shape.fDZ
  540. ];
  541. return create8edgesBuffer(v, faces_limit);
  542. }
  543. /** @summary Creates arb8 geometry
  544. * @private */
  545. function createArb8Buffer(shape, faces_limit) {
  546. if (faces_limit < 0) return 12;
  547. const vertices = [
  548. shape.fXY[0][0], shape.fXY[0][1], -shape.fDZ,
  549. shape.fXY[1][0], shape.fXY[1][1], -shape.fDZ,
  550. shape.fXY[2][0], shape.fXY[2][1], -shape.fDZ,
  551. shape.fXY[3][0], shape.fXY[3][1], -shape.fDZ,
  552. shape.fXY[4][0], shape.fXY[4][1], shape.fDZ,
  553. shape.fXY[5][0], shape.fXY[5][1], shape.fDZ,
  554. shape.fXY[6][0], shape.fXY[6][1], shape.fDZ,
  555. shape.fXY[7][0], shape.fXY[7][1], shape.fDZ
  556. ],
  557. indicies = [
  558. 4, 7, 6, 6, 5, 4, 3, 7, 4, 4, 0, 3,
  559. 5, 1, 0, 0, 4, 5, 6, 2, 1, 1, 5, 6,
  560. 7, 3, 2, 2, 6, 7, 1, 2, 3, 3, 0, 1];
  561. // detect same vertices on both Z-layers
  562. for (let side = 0; side < vertices.length; side += vertices.length/2) {
  563. for (let n1 = side; n1 < side + vertices.length/2 - 3; n1+=3) {
  564. for (let n2 = n1+3; n2 < side + vertices.length/2; n2+=3) {
  565. if ((vertices[n1] === vertices[n2]) &&
  566. (vertices[n1+1] === vertices[n2+1]) &&
  567. (vertices[n1+2] === vertices[n2+2])) {
  568. for (let k=0; k<indicies.length; ++k)
  569. if (indicies[k] === n2/3) indicies[k] = n1/3;
  570. }
  571. }
  572. }
  573. }
  574. const map = []; // list of existing faces (with all rotations)
  575. let numfaces = 0;
  576. for (let k = 0; k < indicies.length; k += 3) {
  577. const id1 = indicies[k]*100 + indicies[k+1]*10 + indicies[k+2],
  578. id2 = indicies[k+1]*100 + indicies[k+2]*10 + indicies[k],
  579. id3 = indicies[k+2]*100 + indicies[k]*10 + indicies[k+1];
  580. if ((indicies[k] === indicies[k+1]) || (indicies[k] === indicies[k+2]) || (indicies[k+1] === indicies[k+2]) ||
  581. (map.indexOf(id1) >= 0) || (map.indexOf(id2) >= 0) || (map.indexOf(id3) >= 0))
  582. indicies[k] = indicies[k+1] = indicies[k+2] = -1;
  583. else {
  584. map.push(id1, id2, id3);
  585. numfaces++;
  586. }
  587. }
  588. const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces);
  589. for (let n = 0; n < indicies.length; n += 6) {
  590. const i1 = indicies[n] * 3,
  591. i2 = indicies[n+1] * 3,
  592. i3 = indicies[n+2] * 3,
  593. i4 = indicies[n+3] * 3,
  594. i5 = indicies[n+4] * 3,
  595. i6 = indicies[n+5] * 3;
  596. let norm = null;
  597. if ((i1 >= 0) && (i4 >= 0) && faces_limit) {
  598. // try to identify two faces with same normal - very useful if one can create face4
  599. if (n === 0)
  600. norm = new THREE.Vector3(0, 0, 1);
  601. else if (n === 30)
  602. norm = new THREE.Vector3(0, 0, -1);
  603. else {
  604. const norm1 = produceNormal(vertices[i1], vertices[i1+1], vertices[i1+2],
  605. vertices[i2], vertices[i2+1], vertices[i2+2],
  606. vertices[i3], vertices[i3+1], vertices[i3+2]);
  607. norm1.normalize();
  608. const norm2 = produceNormal(vertices[i4], vertices[i4+1], vertices[i4+2],
  609. vertices[i5], vertices[i5+1], vertices[i5+2],
  610. vertices[i6], vertices[i6+1], vertices[i6+2]);
  611. norm2.normalize();
  612. if (norm1.distanceToSquared(norm2) < 1e-12) norm = norm1;
  613. }
  614. }
  615. if (norm !== null) {
  616. creator.addFace4(vertices[i1], vertices[i1+1], vertices[i1+2],
  617. vertices[i2], vertices[i2+1], vertices[i2+2],
  618. vertices[i3], vertices[i3+1], vertices[i3+2],
  619. vertices[i5], vertices[i5+1], vertices[i5+2]);
  620. creator.setNormal(norm.x, norm.y, norm.z);
  621. } else {
  622. if (i1 >= 0) {
  623. creator.addFace3(vertices[i1], vertices[i1+1], vertices[i1+2],
  624. vertices[i2], vertices[i2+1], vertices[i2+2],
  625. vertices[i3], vertices[i3+1], vertices[i3+2]);
  626. creator.calcNormal();
  627. }
  628. if (i4 >= 0) {
  629. creator.addFace3(vertices[i4], vertices[i4+1], vertices[i4+2],
  630. vertices[i5], vertices[i5+1], vertices[i5+2],
  631. vertices[i6], vertices[i6+1], vertices[i6+2]);
  632. creator.calcNormal();
  633. }
  634. }
  635. }
  636. return creator.create();
  637. }
  638. /** @summary Creates sphere geometry
  639. * @private */
  640. function createSphereBuffer(shape, faces_limit) {
  641. const radius = [shape.fRmax, shape.fRmin],
  642. phiStart = shape.fPhi1,
  643. phiLength = shape.fPhi2 - shape.fPhi1,
  644. thetaStart = shape.fTheta1,
  645. thetaLength = shape.fTheta2 - shape.fTheta1,
  646. noInside = (radius[1] <= 0);
  647. let widthSegments = shape.fNseg,
  648. heightSegments = shape.fNz;
  649. if (faces_limit > 0) {
  650. const fact = (noInside ? 2 : 4) * widthSegments * heightSegments / faces_limit;
  651. if (fact > 1.0) {
  652. widthSegments = Math.max(4, Math.floor(widthSegments/Math.sqrt(fact)));
  653. heightSegments = Math.max(4, Math.floor(heightSegments/Math.sqrt(fact)));
  654. }
  655. }
  656. let numoutside = widthSegments * heightSegments * 2,
  657. numtop = widthSegments * (noInside ? 1 : 2),
  658. numbottom = widthSegments * (noInside ? 1 : 2);
  659. const numcut = (phiLength === 360) ? 0 : heightSegments * (noInside ? 2 : 4),
  660. epsilon = 1e-10;
  661. if (faces_limit < 0) return numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut;
  662. const _sinp = new Float32Array(widthSegments+1),
  663. _cosp = new Float32Array(widthSegments+1),
  664. _sint = new Float32Array(heightSegments+1),
  665. _cost = new Float32Array(heightSegments+1);
  666. for (let n = 0; n <= heightSegments; ++n) {
  667. const theta = (thetaStart + thetaLength/heightSegments*n)*Math.PI/180;
  668. _sint[n] = Math.sin(theta);
  669. _cost[n] = Math.cos(theta);
  670. }
  671. for (let n = 0; n <= widthSegments; ++n) {
  672. const phi = (phiStart + phiLength/widthSegments*n)*Math.PI/180;
  673. _sinp[n] = Math.sin(phi);
  674. _cosp[n] = Math.cos(phi);
  675. }
  676. if (Math.abs(_sint[0]) <= epsilon) { numoutside -= widthSegments; numtop = 0; }
  677. if (Math.abs(_sint[heightSegments]) <= epsilon) { numoutside -= widthSegments; numbottom = 0; }
  678. const numfaces = numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut,
  679. creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces);
  680. for (let side = 0; side < 2; ++side) {
  681. if ((side === 1) && noInside) break;
  682. const r = radius[side],
  683. s = (side === 0) ? 1 : -1,
  684. d1 = 1 - side, d2 = 1 - d1;
  685. // use direct algorithm for the sphere - here normals and position can be calculated directly
  686. for (let k = 0; k < heightSegments; ++k) {
  687. const k1 = k + d1, k2 = k + d2;
  688. let skip = 0;
  689. if (Math.abs(_sint[k1]) <= epsilon) skip = 1; else
  690. if (Math.abs(_sint[k2]) <= epsilon) skip = 2;
  691. for (let n = 0; n < widthSegments; ++n) {
  692. creator.addFace4(
  693. r*_sint[k1]*_cosp[n], r*_sint[k1] *_sinp[n], r*_cost[k1],
  694. r*_sint[k1]*_cosp[n+1], r*_sint[k1] *_sinp[n+1], r*_cost[k1],
  695. r*_sint[k2]*_cosp[n+1], r*_sint[k2] *_sinp[n+1], r*_cost[k2],
  696. r*_sint[k2]*_cosp[n], r*_sint[k2] *_sinp[n], r*_cost[k2],
  697. skip);
  698. creator.setNormal4(
  699. s*_sint[k1]*_cosp[n], s*_sint[k1] *_sinp[n], s*_cost[k1],
  700. s*_sint[k1]*_cosp[n+1], s*_sint[k1] *_sinp[n+1], s*_cost[k1],
  701. s*_sint[k2]*_cosp[n+1], s*_sint[k2] *_sinp[n+1], s*_cost[k2],
  702. s*_sint[k2]*_cosp[n], s*_sint[k2] *_sinp[n], s*_cost[k2],
  703. skip);
  704. }
  705. }
  706. }
  707. // top/bottom
  708. for (let side = 0; side <= heightSegments; side += heightSegments) {
  709. if (Math.abs(_sint[side]) >= epsilon) {
  710. const ss = _sint[side], cc = _cost[side],
  711. d1 = (side === 0) ? 0 : 1, d2 = 1 - d1;
  712. for (let n = 0; n < widthSegments; ++n) {
  713. creator.addFace4(
  714. radius[1] * ss * _cosp[n+d1], radius[1] * ss * _sinp[n+d1], radius[1] * cc,
  715. radius[0] * ss * _cosp[n+d1], radius[0] * ss * _sinp[n+d1], radius[0] * cc,
  716. radius[0] * ss * _cosp[n+d2], radius[0] * ss * _sinp[n+d2], radius[0] * cc,
  717. radius[1] * ss * _cosp[n+d2], radius[1] * ss * _sinp[n+d2], radius[1] * cc,
  718. noInside ? 2 : 0);
  719. creator.calcNormal();
  720. }
  721. }
  722. }
  723. // cut left/right sides
  724. if (phiLength < 360) {
  725. for (let side = 0; side <= widthSegments; side += widthSegments) {
  726. const ss = _sinp[side], cc = _cosp[side],
  727. d1 = (side === 0) ? 1 : 0, d2 = 1 - d1;
  728. for (let k=0; k<heightSegments; ++k) {
  729. creator.addFace4(
  730. radius[1] * _sint[k+d1] * cc, radius[1] * _sint[k+d1] * ss, radius[1] * _cost[k+d1],
  731. radius[0] * _sint[k+d1] * cc, radius[0] * _sint[k+d1] * ss, radius[0] * _cost[k+d1],
  732. radius[0] * _sint[k+d2] * cc, radius[0] * _sint[k+d2] * ss, radius[0] * _cost[k+d2],
  733. radius[1] * _sint[k+d2] * cc, radius[1] * _sint[k+d2] * ss, radius[1] * _cost[k+d2],
  734. noInside ? 2 : 0);
  735. creator.calcNormal();
  736. }
  737. }
  738. }
  739. return creator.create();
  740. }
  741. /** @summary Creates tube geometry
  742. * @private */
  743. function createTubeBuffer(shape, faces_limit) {
  744. let outerR, innerR; // inner/outer tube radius
  745. if ((shape._typename === clTGeoCone) || (shape._typename === clTGeoConeSeg)) {
  746. outerR = [shape.fRmax2, shape.fRmax1];
  747. innerR = [shape.fRmin2, shape.fRmin1];
  748. } else {
  749. outerR = [shape.fRmax, shape.fRmax];
  750. innerR = [shape.fRmin, shape.fRmin];
  751. }
  752. const hasrmin = (innerR[0] > 0) || (innerR[1] > 0);
  753. let thetaStart = 0, thetaLength = 360;
  754. if ((shape._typename === clTGeoConeSeg) || (shape._typename === clTGeoTubeSeg) || (shape._typename === clTGeoCtub)) {
  755. thetaStart = shape.fPhi1;
  756. thetaLength = shape.fPhi2 - shape.fPhi1;
  757. }
  758. const radiusSegments = Math.max(4, Math.round(thetaLength / _cfg.GradPerSegm));
  759. // external surface
  760. let numfaces = radiusSegments * (((outerR[0] <= 0) || (outerR[1] <= 0)) ? 1 : 2);
  761. // internal surface
  762. if (hasrmin)
  763. numfaces += radiusSegments * (((innerR[0] <= 0) || (innerR[1] <= 0)) ? 1 : 2);
  764. // upper cap
  765. if (outerR[0] > 0) numfaces += radiusSegments * ((innerR[0] > 0) ? 2 : 1);
  766. // bottom cup
  767. if (outerR[1] > 0) numfaces += radiusSegments * ((innerR[1] > 0) ? 2 : 1);
  768. if (thetaLength < 360)
  769. numfaces += ((outerR[0] > innerR[0]) ? 2 : 0) + ((outerR[1] > innerR[1]) ? 2 : 0);
  770. if (faces_limit < 0) return numfaces;
  771. const phi0 = thetaStart*Math.PI/180,
  772. dphi = thetaLength/radiusSegments*Math.PI/180,
  773. _sin = new Float32Array(radiusSegments+1),
  774. _cos = new Float32Array(radiusSegments+1);
  775. for (let seg = 0; seg <= radiusSegments; ++seg) {
  776. _cos[seg] = Math.cos(phi0+seg*dphi);
  777. _sin[seg] = Math.sin(phi0+seg*dphi);
  778. }
  779. const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces),
  780. calcZ = (shape._typename !== clTGeoCtub)
  781. ? null
  782. : (x, y, z) => {
  783. const arr = (z < 0) ? shape.fNlow : shape.fNhigh;
  784. return ((z < 0) ? -shape.fDz : shape.fDz) - (x*arr[0] + y*arr[1]) / arr[2];
  785. };
  786. // create outer/inner tube
  787. for (let side = 0; side < 2; ++side) {
  788. if ((side === 1) && !hasrmin) break;
  789. const R = (side === 0) ? outerR : innerR, d1 = side, d2 = 1 - side;
  790. let nxy = 1, nz = 0;
  791. if (R[0] !== R[1]) {
  792. const angle = Math.atan2((R[1]-R[0]), 2*shape.fDZ);
  793. nxy = Math.cos(angle);
  794. nz = Math.sin(angle);
  795. }
  796. if (side === 1) { nxy *= -1; nz *= -1; }
  797. const reduce = (R[0] <= 0) ? 2 : ((R[1] <= 0) ? 1 : 0);
  798. for (let seg = 0; seg < radiusSegments; ++seg) {
  799. creator.addFace4(
  800. R[0] * _cos[seg+d1], R[0] * _sin[seg+d1], shape.fDZ,
  801. R[1] * _cos[seg+d1], R[1] * _sin[seg+d1], -shape.fDZ,
  802. R[1] * _cos[seg+d2], R[1] * _sin[seg+d2], -shape.fDZ,
  803. R[0] * _cos[seg+d2], R[0] * _sin[seg+d2], shape.fDZ,
  804. reduce);
  805. if (calcZ) creator.recalcZ(calcZ);
  806. creator.setNormal_12_34(nxy*_cos[seg+d1], nxy*_sin[seg+d1], nz,
  807. nxy*_cos[seg+d2], nxy*_sin[seg+d2], nz,
  808. reduce);
  809. }
  810. }
  811. // create upper/bottom part
  812. for (let side = 0; side < 2; ++side) {
  813. if (outerR[side] <= 0) continue;
  814. const d1 = side, d2 = 1- side,
  815. sign = (side === 0) ? 1 : -1,
  816. reduce = (innerR[side] <= 0) ? 2 : 0;
  817. if ((reduce === 2) && (thetaLength === 360) && !calcZ)
  818. creator.startPolygon(side === 0);
  819. for (let seg = 0; seg < radiusSegments; ++seg) {
  820. creator.addFace4(
  821. innerR[side] * _cos[seg+d1], innerR[side] * _sin[seg+d1], sign*shape.fDZ,
  822. outerR[side] * _cos[seg+d1], outerR[side] * _sin[seg+d1], sign*shape.fDZ,
  823. outerR[side] * _cos[seg+d2], outerR[side] * _sin[seg+d2], sign*shape.fDZ,
  824. innerR[side] * _cos[seg+d2], innerR[side] * _sin[seg+d2], sign*shape.fDZ,
  825. reduce);
  826. if (calcZ) {
  827. creator.recalcZ(calcZ);
  828. creator.calcNormal();
  829. } else
  830. creator.setNormal(0, 0, sign);
  831. }
  832. creator.stopPolygon();
  833. }
  834. // create cut surfaces
  835. if (thetaLength < 360) {
  836. creator.addFace4(innerR[1] * _cos[0], innerR[1] * _sin[0], -shape.fDZ,
  837. outerR[1] * _cos[0], outerR[1] * _sin[0], -shape.fDZ,
  838. outerR[0] * _cos[0], outerR[0] * _sin[0], shape.fDZ,
  839. innerR[0] * _cos[0], innerR[0] * _sin[0], shape.fDZ,
  840. (outerR[0] === innerR[0]) ? 2 : ((innerR[1] === outerR[1]) ? 1 : 0));
  841. if (calcZ) creator.recalcZ(calcZ);
  842. creator.calcNormal();
  843. creator.addFace4(innerR[0] * _cos[radiusSegments], innerR[0] * _sin[radiusSegments], shape.fDZ,
  844. outerR[0] * _cos[radiusSegments], outerR[0] * _sin[radiusSegments], shape.fDZ,
  845. outerR[1] * _cos[radiusSegments], outerR[1] * _sin[radiusSegments], -shape.fDZ,
  846. innerR[1] * _cos[radiusSegments], innerR[1] * _sin[radiusSegments], -shape.fDZ,
  847. (outerR[0] === innerR[0]) ? 1 : ((innerR[1] === outerR[1]) ? 2 : 0));
  848. if (calcZ) creator.recalcZ(calcZ);
  849. creator.calcNormal();
  850. }
  851. return creator.create();
  852. }
  853. /** @summary Creates eltu geometry
  854. * @private */
  855. function createEltuBuffer(shape, faces_limit) {
  856. const radiusSegments = Math.max(4, Math.round(360 / _cfg.GradPerSegm));
  857. if (faces_limit < 0) return radiusSegments*4;
  858. // calculate all sin/cos tables in advance
  859. const x = new Float32Array(radiusSegments+1),
  860. y = new Float32Array(radiusSegments+1);
  861. for (let seg=0; seg<=radiusSegments; ++seg) {
  862. const phi = seg/radiusSegments*2*Math.PI;
  863. x[seg] = shape.fRmin*Math.cos(phi);
  864. y[seg] = shape.fRmax*Math.sin(phi);
  865. }
  866. const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(radiusSegments*4);
  867. let nx1, ny1, nx2 = 1, ny2 = 0;
  868. // create tube faces
  869. for (let seg = 0; seg < radiusSegments; ++seg) {
  870. creator.addFace4(x[seg], y[seg], shape.fDZ,
  871. x[seg], y[seg], -shape.fDZ,
  872. x[seg+1], y[seg+1], -shape.fDZ,
  873. x[seg+1], y[seg+1], shape.fDZ);
  874. // calculate normals ourself
  875. nx1 = nx2; ny1 = ny2;
  876. nx2 = x[seg+1] * shape.fRmax / shape.fRmin;
  877. ny2 = y[seg+1] * shape.fRmin / shape.fRmax;
  878. const dist = Math.sqrt(nx2**2 + ny2**2);
  879. nx2 /= dist;
  880. ny2 /= dist;
  881. creator.setNormal_12_34(nx1, ny1, 0, nx2, ny2, 0);
  882. }
  883. // create top/bottom sides
  884. for (let side = 0; side < 2; ++side) {
  885. const sign = (side === 0) ? 1 : -1, d1 = side, d2 = 1 - side;
  886. for (let seg=0; seg<radiusSegments; ++seg) {
  887. creator.addFace3(0, 0, sign*shape.fDZ,
  888. x[seg+d1], y[seg+d1], sign*shape.fDZ,
  889. x[seg+d2], y[seg+d2], sign*shape.fDZ);
  890. creator.setNormal(0, 0, sign);
  891. }
  892. }
  893. return creator.create();
  894. }
  895. /** @summary Creates torus geometry
  896. * @private */
  897. function createTorusBuffer(shape, faces_limit) {
  898. const radius = shape.fR;
  899. let radialSegments = Math.max(6, Math.round(360 / _cfg.GradPerSegm)),
  900. tubularSegments = Math.max(8, Math.round(shape.fDphi / _cfg.GradPerSegm)),
  901. numfaces = (shape.fRmin > 0 ? 4 : 2) * radialSegments * (tubularSegments + (shape.fDphi !== 360 ? 1 : 0));
  902. if (faces_limit < 0) return numfaces;
  903. if ((faces_limit > 0) && (numfaces > faces_limit)) {
  904. radialSegments = Math.floor(radialSegments/Math.sqrt(numfaces / faces_limit));
  905. tubularSegments = Math.floor(tubularSegments/Math.sqrt(numfaces / faces_limit));
  906. numfaces = (shape.fRmin > 0 ? 4 : 2) * radialSegments * (tubularSegments + (shape.fDphi !== 360 ? 1 : 0));
  907. }
  908. const _sinr = new Float32Array(radialSegments+1),
  909. _cosr = new Float32Array(radialSegments+1),
  910. _sint = new Float32Array(tubularSegments+1),
  911. _cost = new Float32Array(tubularSegments+1);
  912. for (let n = 0; n <= radialSegments; ++n) {
  913. _sinr[n] = Math.sin(n/radialSegments*2*Math.PI);
  914. _cosr[n] = Math.cos(n/radialSegments*2*Math.PI);
  915. }
  916. for (let t = 0; t <= tubularSegments; ++t) {
  917. const angle = (shape.fPhi1 + shape.fDphi*t/tubularSegments)/180*Math.PI;
  918. _sint[t] = Math.sin(angle);
  919. _cost[t] = Math.cos(angle);
  920. }
  921. const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces),
  922. // use vectors for normals calculation
  923. p1 = new THREE.Vector3(), p2 = new THREE.Vector3(), p3 = new THREE.Vector3(), p4 = new THREE.Vector3(),
  924. n1 = new THREE.Vector3(), n2 = new THREE.Vector3(), n3 = new THREE.Vector3(), n4 = new THREE.Vector3(),
  925. center1 = new THREE.Vector3(), center2 = new THREE.Vector3();
  926. for (let side = 0; side < 2; ++side) {
  927. if ((side > 0) && (shape.fRmin <= 0)) break;
  928. const tube = (side > 0) ? shape.fRmin : shape.fRmax,
  929. d1 = 1 - side, d2 = 1 - d1, ns = side > 0 ? -1 : 1;
  930. for (let t = 0; t < tubularSegments; ++t) {
  931. const t1 = t + d1, t2 = t + d2;
  932. center1.x = radius * _cost[t1]; center1.y = radius * _sint[t1];
  933. center2.x = radius * _cost[t2]; center2.y = radius * _sint[t2];
  934. for (let n = 0; n < radialSegments; ++n) {
  935. p1.x = (radius + tube * _cosr[n]) * _cost[t1]; p1.y = (radius + tube * _cosr[n]) * _sint[t1]; p1.z = tube*_sinr[n];
  936. p2.x = (radius + tube * _cosr[n+1]) * _cost[t1]; p2.y = (radius + tube * _cosr[n+1]) * _sint[t1]; p2.z = tube*_sinr[n+1];
  937. p3.x = (radius + tube * _cosr[n+1]) * _cost[t2]; p3.y = (radius + tube * _cosr[n+1]) * _sint[t2]; p3.z = tube*_sinr[n+1];
  938. p4.x = (radius + tube * _cosr[n]) * _cost[t2]; p4.y = (radius + tube * _cosr[n]) * _sint[t2]; p4.z = tube*_sinr[n];
  939. creator.addFace4(p1.x, p1.y, p1.z,
  940. p2.x, p2.y, p2.z,
  941. p3.x, p3.y, p3.z,
  942. p4.x, p4.y, p4.z);
  943. n1.subVectors(p1, center1).normalize();
  944. n2.subVectors(p2, center1).normalize();
  945. n3.subVectors(p3, center2).normalize();
  946. n4.subVectors(p4, center2).normalize();
  947. creator.setNormal4(ns*n1.x, ns*n1.y, ns*n1.z,
  948. ns*n2.x, ns*n2.y, ns*n2.z,
  949. ns*n3.x, ns*n3.y, ns*n3.z,
  950. ns*n4.x, ns*n4.y, ns*n4.z);
  951. }
  952. }
  953. }
  954. if (shape.fDphi !== 360) {
  955. for (let t = 0; t <= tubularSegments; t += tubularSegments) {
  956. const tube1 = shape.fRmax, tube2 = shape.fRmin,
  957. d1 = t > 0 ? 0 : 1, d2 = 1 - d1,
  958. skip = shape.fRmin > 0 ? 0 : 1,
  959. nsign = t > 0 ? 1 : -1;
  960. for (let n = 0; n < radialSegments; ++n) {
  961. creator.addFace4((radius + tube1 * _cosr[n+d1]) * _cost[t], (radius + tube1 * _cosr[n+d1]) * _sint[t], tube1*_sinr[n+d1],
  962. (radius + tube2 * _cosr[n+d1]) * _cost[t], (radius + tube2 * _cosr[n+d1]) * _sint[t], tube2*_sinr[n+d1],
  963. (radius + tube2 * _cosr[n+d2]) * _cost[t], (radius + tube2 * _cosr[n+d2]) * _sint[t], tube2*_sinr[n+d2],
  964. (radius + tube1 * _cosr[n+d2]) * _cost[t], (radius + tube1 * _cosr[n+d2]) * _sint[t], tube1*_sinr[n+d2], skip);
  965. creator.setNormal(-nsign * _sint[t], nsign * _cost[t], 0);
  966. }
  967. }
  968. }
  969. return creator.create();
  970. }
  971. /** @summary Creates polygon geometry
  972. * @private */
  973. function createPolygonBuffer(shape, faces_limit) {
  974. const thetaStart = shape.fPhi1,
  975. thetaLength = shape.fDphi;
  976. let radiusSegments, factor;
  977. if (shape._typename === clTGeoPgon) {
  978. radiusSegments = shape.fNedges;
  979. factor = 1.0 / Math.cos(Math.PI/180 * thetaLength / radiusSegments / 2);
  980. } else {
  981. radiusSegments = Math.max(5, Math.round(thetaLength / _cfg.GradPerSegm));
  982. factor = 1;
  983. }
  984. const usage = new Int16Array(2*shape.fNz);
  985. let numusedlayers = 0, hasrmin = false;
  986. for (let layer = 0; layer < shape.fNz; ++layer)
  987. hasrmin = hasrmin || (shape.fRmin[layer] > 0);
  988. // return very rough estimation, number of faces may be much less
  989. if (faces_limit < 0)
  990. return (hasrmin ? 4 : 2) * radiusSegments * (shape.fNz-1);
  991. // coordinate of point on cut edge (x,z)
  992. const pnts = (thetaLength === 360) ? null : [];
  993. // first analyze levels - if we need to create all of them
  994. for (let side = 0; side < 2; ++side) {
  995. const rside = (side === 0) ? 'fRmax' : 'fRmin';
  996. for (let layer=0; layer < shape.fNz; ++layer) {
  997. // first create points for the layer
  998. const layerz = shape.fZ[layer], rad = shape[rside][layer];
  999. usage[layer*2+side] = 0;
  1000. if ((layer > 0) && (layer < shape.fNz-1)) {
  1001. if (((shape.fZ[layer-1] === layerz) && (shape[rside][layer-1] === rad)) ||
  1002. ((shape[rside][layer+1] === rad) && (shape[rside][layer-1] === rad))) {
  1003. // same Z and R as before - ignore
  1004. // or same R before and after
  1005. continue;
  1006. }
  1007. }
  1008. if ((layer > 0) && ((side === 0) || hasrmin)) {
  1009. usage[layer*2+side] = 1;
  1010. numusedlayers++;
  1011. }
  1012. if (pnts !== null) {
  1013. if (side === 0)
  1014. pnts.push(new THREE.Vector2(factor*rad, layerz));
  1015. else if (rad < shape.fRmax[layer])
  1016. pnts.unshift(new THREE.Vector2(factor*rad, layerz));
  1017. }
  1018. }
  1019. }
  1020. let numfaces = numusedlayers*radiusSegments*2;
  1021. if (shape.fRmin[0] !== shape.fRmax[0])
  1022. numfaces += radiusSegments * (hasrmin ? 2 : 1);
  1023. if (shape.fRmin[shape.fNz-1] !== shape.fRmax[shape.fNz-1])
  1024. numfaces += radiusSegments * (hasrmin ? 2 : 1);
  1025. let cut_faces = null;
  1026. if (pnts !== null) {
  1027. if (pnts.length === shape.fNz * 2) {
  1028. // special case - all layers are there, create faces ourself
  1029. cut_faces = [];
  1030. for (let layer = shape.fNz-1; layer > 0; --layer) {
  1031. if (shape.fZ[layer] === shape.fZ[layer-1]) continue;
  1032. const right = 2*shape.fNz - 1 - layer;
  1033. cut_faces.push([right, layer - 1, layer]);
  1034. cut_faces.push([right, right + 1, layer-1]);
  1035. }
  1036. } else {
  1037. // let three.js calculate our faces
  1038. cut_faces = THREE.ShapeUtils.triangulateShape(pnts, []);
  1039. }
  1040. numfaces += cut_faces.length*2;
  1041. }
  1042. const phi0 = thetaStart*Math.PI/180,
  1043. dphi = thetaLength/radiusSegments*Math.PI/180,
  1044. // calculate all sin/cos tables in advance
  1045. _sin = new Float32Array(radiusSegments+1),
  1046. _cos = new Float32Array(radiusSegments+1);
  1047. for (let seg = 0; seg <= radiusSegments; ++seg) {
  1048. _cos[seg] = Math.cos(phi0+seg*dphi);
  1049. _sin[seg] = Math.sin(phi0+seg*dphi);
  1050. }
  1051. const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces);
  1052. // add sides
  1053. for (let side = 0; side < 2; ++side) {
  1054. const rside = (side === 0) ? 'fRmax' : 'fRmin',
  1055. d1 = 1 - side, d2 = side;
  1056. let z1 = shape.fZ[0], r1 = factor*shape[rside][0];
  1057. for (let layer = 0; layer < shape.fNz; ++layer) {
  1058. if (usage[layer*2+side] === 0) continue;
  1059. const z2 = shape.fZ[layer], r2 = factor*shape[rside][layer];
  1060. let nxy = 1, nz = 0;
  1061. if ((r2 !== r1)) {
  1062. const angle = Math.atan2((r2-r1), (z2-z1));
  1063. nxy = Math.cos(angle);
  1064. nz = Math.sin(angle);
  1065. }
  1066. if (side > 0) { nxy*=-1; nz*=-1; }
  1067. for (let seg = 0; seg < radiusSegments; ++seg) {
  1068. creator.addFace4(r1 * _cos[seg+d1], r1 * _sin[seg+d1], z1,
  1069. r2 * _cos[seg+d1], r2 * _sin[seg+d1], z2,
  1070. r2 * _cos[seg+d2], r2 * _sin[seg+d2], z2,
  1071. r1 * _cos[seg+d2], r1 * _sin[seg+d2], z1);
  1072. creator.setNormal_12_34(nxy*_cos[seg+d1], nxy*_sin[seg+d1], nz, nxy*_cos[seg+d2], nxy*_sin[seg+d2], nz);
  1073. }
  1074. z1 = z2; r1 = r2;
  1075. }
  1076. }
  1077. // add top/bottom
  1078. for (let layer = 0; layer < shape.fNz; layer += (shape.fNz-1)) {
  1079. const rmin = factor*shape.fRmin[layer], rmax = factor*shape.fRmax[layer];
  1080. if (rmin === rmax) continue;
  1081. const layerz = shape.fZ[layer],
  1082. d1 = (layer === 0) ? 1 : 0, d2 = 1 - d1,
  1083. normalz = (layer === 0) ? -1: 1;
  1084. if (!hasrmin && !cut_faces)
  1085. creator.startPolygon(layer > 0);
  1086. for (let seg = 0; seg < radiusSegments; ++seg) {
  1087. creator.addFace4(rmin * _cos[seg+d1], rmin * _sin[seg+d1], layerz,
  1088. rmax * _cos[seg+d1], rmax * _sin[seg+d1], layerz,
  1089. rmax * _cos[seg+d2], rmax * _sin[seg+d2], layerz,
  1090. rmin * _cos[seg+d2], rmin * _sin[seg+d2], layerz,
  1091. hasrmin ? 0 : 2);
  1092. creator.setNormal(0, 0, normalz);
  1093. }
  1094. creator.stopPolygon();
  1095. }
  1096. if (cut_faces) {
  1097. for (let seg = 0; seg <= radiusSegments; seg += radiusSegments) {
  1098. const d1 = (seg === 0) ? 1 : 2, d2 = 3 - d1;
  1099. for (let n=0; n<cut_faces.length; ++n) {
  1100. const a = pnts[cut_faces[n][0]],
  1101. b = pnts[cut_faces[n][d1]],
  1102. c = pnts[cut_faces[n][d2]];
  1103. creator.addFace3(a.x * _cos[seg], a.x * _sin[seg], a.y,
  1104. b.x * _cos[seg], b.x * _sin[seg], b.y,
  1105. c.x * _cos[seg], c.x * _sin[seg], c.y);
  1106. creator.calcNormal();
  1107. }
  1108. }
  1109. }
  1110. return creator.create();
  1111. }
  1112. /** @summary Creates xtru geometry
  1113. * @private */
  1114. function createXtruBuffer(shape, faces_limit) {
  1115. let nfaces = (shape.fNz-1) * shape.fNvert * 2;
  1116. if (faces_limit < 0)
  1117. return nfaces + shape.fNvert*3;
  1118. // create points
  1119. const pnts = [];
  1120. for (let vert = 0; vert < shape.fNvert; ++vert)
  1121. pnts.push(new THREE.Vector2(shape.fX[vert], shape.fY[vert]));
  1122. let faces = THREE.ShapeUtils.triangulateShape(pnts, []);
  1123. if (faces.length < pnts.length - 2) {
  1124. geoWarn(`Problem with XTRU shape ${shape.fName} with ${pnts.length} vertices`);
  1125. faces = [];
  1126. } else
  1127. nfaces += faces.length * 2;
  1128. const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(nfaces);
  1129. for (let layer = 0; layer < shape.fNz-1; ++layer) {
  1130. const z1 = shape.fZ[layer], scale1 = shape.fScale[layer],
  1131. z2 = shape.fZ[layer+1], scale2 = shape.fScale[layer+1],
  1132. x01 = shape.fX0[layer], x02 = shape.fX0[layer+1],
  1133. y01 = shape.fY0[layer], y02 = shape.fY0[layer+1];
  1134. for (let vert1 = 0; vert1 < shape.fNvert; ++vert1) {
  1135. const vert2 = (vert1+1) % shape.fNvert;
  1136. creator.addFace4(scale1 * shape.fX[vert1] + x01, scale1 * shape.fY[vert1] + y01, z1,
  1137. scale2 * shape.fX[vert1] + x02, scale2 * shape.fY[vert1] + y02, z2,
  1138. scale2 * shape.fX[vert2] + x02, scale2 * shape.fY[vert2] + y02, z2,
  1139. scale1 * shape.fX[vert2] + x01, scale1 * shape.fY[vert2] + y01, z1);
  1140. creator.calcNormal();
  1141. }
  1142. }
  1143. for (let layer = 0; layer <= shape.fNz-1; layer += (shape.fNz-1)) {
  1144. const z = shape.fZ[layer], scale = shape.fScale[layer],
  1145. x0 = shape.fX0[layer], y0 = shape.fY0[layer];
  1146. for (let n = 0; n < faces.length; ++n) {
  1147. const face = faces[n],
  1148. pnt1 = pnts[face[0]],
  1149. pnt2 = pnts[face[layer === 0 ? 2 : 1]],
  1150. pnt3 = pnts[face[layer === 0 ? 1 : 2]];
  1151. creator.addFace3(scale * pnt1.x + x0, scale * pnt1.y + y0, z,
  1152. scale * pnt2.x + x0, scale * pnt2.y + y0, z,
  1153. scale * pnt3.x + x0, scale * pnt3.y + y0, z);
  1154. creator.setNormal(0, 0, layer === 0 ? -1 : 1);
  1155. }
  1156. }
  1157. return creator.create();
  1158. }
  1159. /** @summary Creates para geometry
  1160. * @private */
  1161. function createParaboloidBuffer(shape, faces_limit) {
  1162. let radiusSegments = Math.max(4, Math.round(360 / _cfg.GradPerSegm)),
  1163. heightSegments = 30;
  1164. if (faces_limit > 0) {
  1165. const fact = 2*radiusSegments*(heightSegments+1) / faces_limit;
  1166. if (fact > 1.0) {
  1167. radiusSegments = Math.max(5, Math.floor(radiusSegments/Math.sqrt(fact)));
  1168. heightSegments = Math.max(5, Math.floor(heightSegments/Math.sqrt(fact)));
  1169. }
  1170. }
  1171. const rmin = shape.fRlo, rmax = shape.fRhi;
  1172. let numfaces = (heightSegments+1) * radiusSegments*2;
  1173. if (rmin === 0) numfaces -= radiusSegments*2; // complete layer
  1174. if (rmax === 0) numfaces -= radiusSegments*2; // complete layer
  1175. if (faces_limit < 0) return numfaces;
  1176. let zmin = -shape.fDZ, zmax = shape.fDZ;
  1177. // if no radius at -z, find intersection
  1178. if (shape.fA >= 0)
  1179. zmin = Math.max(zmin, shape.fB);
  1180. else
  1181. zmax = Math.min(shape.fB, zmax);
  1182. const ttmin = Math.atan2(zmin, rmin),
  1183. ttmax = Math.atan2(zmax, rmax),
  1184. // calculate all sin/cos tables in advance
  1185. _sin = new Float32Array(radiusSegments+1),
  1186. _cos = new Float32Array(radiusSegments+1);
  1187. for (let seg = 0; seg <= radiusSegments; ++seg) {
  1188. _cos[seg] = Math.cos(seg/radiusSegments*2*Math.PI);
  1189. _sin[seg] = Math.sin(seg/radiusSegments*2*Math.PI);
  1190. }
  1191. const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces);
  1192. let lastz = zmin, lastr = 0, lastnxy = 0, lastnz = -1;
  1193. for (let layer = 0; layer <= heightSegments + 1; ++layer) {
  1194. if ((layer === 0) && (rmin === 0))
  1195. continue;
  1196. if ((layer === heightSegments + 1) && (lastr === 0))
  1197. break;
  1198. let layerz, radius;
  1199. switch (layer) {
  1200. case 0: layerz = zmin; radius = rmin; break;
  1201. case heightSegments: layerz = zmax; radius = rmax; break;
  1202. case heightSegments + 1: layerz = zmax; radius = 0; break;
  1203. default: {
  1204. const tt = Math.tan(ttmin + (ttmax-ttmin) * layer / heightSegments),
  1205. delta = tt**2 - 4*shape.fA*shape.fB; // should be always positive (a*b < 0)
  1206. radius = 0.5*(tt+Math.sqrt(delta))/shape.fA;
  1207. if (radius < 1e-6) radius = 0;
  1208. layerz = radius*tt;
  1209. }
  1210. }
  1211. const nxy = shape.fA * radius,
  1212. nz = (shape.fA > 0) ? -1 : 1,
  1213. skip = (lastr === 0) ? 1 : ((radius === 0) ? 2 : 0);
  1214. for (let seg = 0; seg < radiusSegments; ++seg) {
  1215. creator.addFace4(radius*_cos[seg], radius*_sin[seg], layerz,
  1216. lastr*_cos[seg], lastr*_sin[seg], lastz,
  1217. lastr*_cos[seg+1], lastr*_sin[seg+1], lastz,
  1218. radius*_cos[seg+1], radius*_sin[seg+1], layerz, skip);
  1219. // use analytic normal values when open/closing paraboloid around 0
  1220. // cut faces (top or bottom) set with simple normal
  1221. if ((skip === 0) || ((layer === 1) && (rmin === 0)) || ((layer === heightSegments+1) && (rmax === 0))) {
  1222. creator.setNormal4(nxy*_cos[seg], nxy*_sin[seg], nz,
  1223. lastnxy*_cos[seg], lastnxy*_sin[seg], lastnz,
  1224. lastnxy*_cos[seg+1], lastnxy*_sin[seg+1], lastnz,
  1225. nxy*_cos[seg+1], nxy*_sin[seg+1], nz, skip);
  1226. } else
  1227. creator.setNormal(0, 0, (layer < heightSegments) ? -1 : 1);
  1228. }
  1229. lastz = layerz; lastr = radius;
  1230. lastnxy = nxy; lastnz = nz;
  1231. }
  1232. return creator.create();
  1233. }
  1234. /** @summary Creates hype geometry
  1235. * @private */
  1236. function createHypeBuffer(shape, faces_limit) {
  1237. if ((shape.fTin === 0) && (shape.fTout === 0))
  1238. return createTubeBuffer(shape, faces_limit);
  1239. let radiusSegments = Math.max(4, Math.round(360 / _cfg.GradPerSegm)),
  1240. heightSegments = 30,
  1241. numfaces = radiusSegments * (heightSegments + 1) * ((shape.fRmin > 0) ? 4 : 2);
  1242. if (faces_limit < 0)
  1243. return numfaces;
  1244. if ((faces_limit > 0) && (faces_limit > numfaces)) {
  1245. radiusSegments = Math.max(4, Math.floor(radiusSegments/Math.sqrt(numfaces/faces_limit)));
  1246. heightSegments = Math.max(4, Math.floor(heightSegments/Math.sqrt(numfaces/faces_limit)));
  1247. numfaces = radiusSegments * (heightSegments + 1) * ((shape.fRmin > 0) ? 4 : 2);
  1248. }
  1249. // calculate all sin/cos tables in advance
  1250. const _sin = new Float32Array(radiusSegments+1),
  1251. _cos = new Float32Array(radiusSegments+1);
  1252. for (let seg=0; seg<=radiusSegments; ++seg) {
  1253. _cos[seg] = Math.cos(seg/radiusSegments*2*Math.PI);
  1254. _sin[seg] = Math.sin(seg/radiusSegments*2*Math.PI);
  1255. }
  1256. const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces);
  1257. // in-out side
  1258. for (let side = 0; side < 2; ++side) {
  1259. if ((side > 0) && (shape.fRmin <= 0)) break;
  1260. const r0 = (side > 0) ? shape.fRmin : shape.fRmax,
  1261. tsq = (side > 0) ? shape.fTinsq : shape.fToutsq,
  1262. d1 = 1- side, d2 = 1 - d1;
  1263. // vertical layers
  1264. for (let layer = 0; layer < heightSegments; ++layer) {
  1265. const z1 = -shape.fDz + layer/heightSegments*2*shape.fDz,
  1266. z2 = -shape.fDz + (layer+1)/heightSegments*2*shape.fDz,
  1267. r1 = Math.sqrt(r0**2 + tsq*z1**2),
  1268. r2 = Math.sqrt(r0**2 + tsq*z2**2);
  1269. for (let seg = 0; seg < radiusSegments; ++seg) {
  1270. creator.addFace4(r1 * _cos[seg+d1], r1 * _sin[seg+d1], z1,
  1271. r2 * _cos[seg+d1], r2 * _sin[seg+d1], z2,
  1272. r2 * _cos[seg+d2], r2 * _sin[seg+d2], z2,
  1273. r1 * _cos[seg+d2], r1 * _sin[seg+d2], z1);
  1274. creator.calcNormal();
  1275. }
  1276. }
  1277. }
  1278. // add caps
  1279. for (let layer = 0; layer < 2; ++layer) {
  1280. const z = (layer === 0) ? shape.fDz : -shape.fDz,
  1281. r1 = Math.sqrt(shape.fRmax**2 + shape.fToutsq*z**2),
  1282. r2 = (shape.fRmin > 0) ? Math.sqrt(shape.fRmin**2 + shape.fTinsq*z**2) : 0,
  1283. skip = (shape.fRmin > 0) ? 0 : 1,
  1284. d1 = 1 - layer, d2 = 1 - d1;
  1285. for (let seg = 0; seg < radiusSegments; ++seg) {
  1286. creator.addFace4(r1 * _cos[seg+d1], r1 * _sin[seg+d1], z,
  1287. r2 * _cos[seg+d1], r2 * _sin[seg+d1], z,
  1288. r2 * _cos[seg+d2], r2 * _sin[seg+d2], z,
  1289. r1 * _cos[seg+d2], r1 * _sin[seg+d2], z, skip);
  1290. creator.setNormal(0, 0, (layer === 0) ? 1 : -1);
  1291. }
  1292. }
  1293. return creator.create();
  1294. }
  1295. /** @summary Creates tessellated geometry
  1296. * @private */
  1297. function createTessellatedBuffer(shape, faces_limit) {
  1298. let numfaces = 0;
  1299. for (let i = 0; i < shape.fFacets.length; ++i)
  1300. numfaces += (shape.fFacets[i].fNvert === 4) ? 2 : 1;
  1301. if (faces_limit < 0) return numfaces;
  1302. const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces);
  1303. for (let i = 0; i < shape.fFacets.length; ++i) {
  1304. const f = shape.fFacets[i],
  1305. v0 = shape.fVertices[f.fIvert[0]].fVec,
  1306. v1 = shape.fVertices[f.fIvert[1]].fVec,
  1307. v2 = shape.fVertices[f.fIvert[2]].fVec;
  1308. if (f.fNvert === 4) {
  1309. const v3 = shape.fVertices[f.fIvert[3]].fVec;
  1310. creator.addFace4(v0[0], v0[1], v0[2], v1[0], v1[1], v1[2], v2[0], v2[1], v2[2], v3[0], v3[1], v3[2]);
  1311. creator.calcNormal();
  1312. } else {
  1313. creator.addFace3(v0[0], v0[1], v0[2], v1[0], v1[1], v1[2], v2[0], v2[1], v2[2]);
  1314. creator.calcNormal();
  1315. }
  1316. }
  1317. return creator.create();
  1318. }
  1319. /** @summary Creates Matrix4 from TGeoMatrix
  1320. * @private */
  1321. function createMatrix(matrix) {
  1322. if (!matrix) return null;
  1323. let translation, rotation, scale;
  1324. switch (matrix._typename) {
  1325. case 'TGeoTranslation': translation = matrix.fTranslation; break;
  1326. case 'TGeoRotation': rotation = matrix.fRotationMatrix; break;
  1327. case 'TGeoScale': scale = matrix.fScale; break;
  1328. case 'TGeoGenTrans':
  1329. scale = matrix.fScale; // no break, translation and rotation follows
  1330. // eslint-disable-next-line no-fallthrough
  1331. case 'TGeoCombiTrans':
  1332. translation = matrix.fTranslation;
  1333. if (matrix.fRotation) rotation = matrix.fRotation.fRotationMatrix;
  1334. break;
  1335. case 'TGeoHMatrix':
  1336. translation = matrix.fTranslation;
  1337. rotation = matrix.fRotationMatrix;
  1338. scale = matrix.fScale;
  1339. break;
  1340. case 'TGeoIdentity':
  1341. break;
  1342. default:
  1343. console.warn(`unsupported matrix ${matrix._typename}`);
  1344. }
  1345. if (!translation && !rotation && !scale) return null;
  1346. const res = new THREE.Matrix4();
  1347. if (rotation) {
  1348. res.set(rotation[0], rotation[1], rotation[2], 0,
  1349. rotation[3], rotation[4], rotation[5], 0,
  1350. rotation[6], rotation[7], rotation[8], 0,
  1351. 0, 0, 0, 1);
  1352. }
  1353. if (translation)
  1354. res.setPosition(translation[0], translation[1], translation[2]);
  1355. if (scale)
  1356. res.scale(new THREE.Vector3(scale[0], scale[1], scale[2]));
  1357. return res;
  1358. }
  1359. /** @summary Creates transformation matrix for TGeoNode
  1360. * @desc created after node visibility flag is checked and volume cut is performed
  1361. * @private */
  1362. function getNodeMatrix(kind, node) {
  1363. let matrix = null;
  1364. if (kind === kindEve) {
  1365. // special handling for EVE nodes
  1366. matrix = new THREE.Matrix4();
  1367. if (node.fTrans) {
  1368. matrix.set(node.fTrans[0], node.fTrans[4], node.fTrans[8], 0,
  1369. node.fTrans[1], node.fTrans[5], node.fTrans[9], 0,
  1370. node.fTrans[2], node.fTrans[6], node.fTrans[10], 0,
  1371. 0, 0, 0, 1);
  1372. // second - set position with proper sign
  1373. matrix.setPosition(node.fTrans[12], node.fTrans[13], node.fTrans[14]);
  1374. }
  1375. } else if (node.fMatrix)
  1376. matrix = createMatrix(node.fMatrix);
  1377. else if ((node._typename === 'TGeoNodeOffset') && node.fFinder) {
  1378. const kPatternReflected = BIT(14),
  1379. finder = node.fFinder,
  1380. typ = finder._typename;
  1381. if ((finder.fBits & kPatternReflected) !== 0)
  1382. geoWarn(`Unsupported reflected pattern ${typ}`);
  1383. if (typ.indexOf('TGeoPattern') !== 0)
  1384. geoWarn(`Abnormal pattern type ${typ}`);
  1385. const part = typ.slice(11);
  1386. matrix = new THREE.Matrix4();
  1387. switch (part) {
  1388. case 'X':
  1389. case 'Y':
  1390. case 'Z':
  1391. case 'ParaX':
  1392. case 'ParaY':
  1393. case 'ParaZ': {
  1394. const _shift = finder.fStart + (node.fIndex + 0.5) * finder.fStep;
  1395. switch (part.at(-1)) {
  1396. case 'X': matrix.setPosition(_shift, 0, 0); break;
  1397. case 'Y': matrix.setPosition(0, _shift, 0); break;
  1398. case 'Z': matrix.setPosition(0, 0, _shift); break;
  1399. }
  1400. break;
  1401. }
  1402. case 'CylPhi': {
  1403. const phi = (Math.PI/180)*(finder.fStart+(node.fIndex+0.5)*finder.fStep),
  1404. _cos = Math.cos(phi), _sin = Math.sin(phi);
  1405. matrix.set(_cos, -_sin, 0, 0,
  1406. _sin, _cos, 0, 0,
  1407. 0, 0, 1, 0,
  1408. 0, 0, 0, 1);
  1409. break;
  1410. }
  1411. case 'CylR':
  1412. // seems to be, require no transformation
  1413. break;
  1414. case 'TrapZ': {
  1415. const dz = finder.fStart + (node.fIndex+0.5)*finder.fStep;
  1416. matrix.setPosition(finder.fTxz*dz, finder.fTyz*dz, dz);
  1417. break;
  1418. }
  1419. // case 'CylR': break;
  1420. // case 'SphR': break;
  1421. // case 'SphTheta': break;
  1422. // case 'SphPhi': break;
  1423. // case 'Honeycomb': break;
  1424. default:
  1425. geoWarn(`Unsupported pattern type ${typ}`);
  1426. break;
  1427. }
  1428. }
  1429. return matrix;
  1430. }
  1431. /** @summary Returns number of faces for provided geometry
  1432. * @param {Object} geom - can be BufferGeometry, CsgGeometry or interim array of polygons
  1433. * @private */
  1434. function numGeometryFaces(geom) {
  1435. if (!geom) return 0;
  1436. if (geom instanceof CsgGeometry)
  1437. return geom.tree.numPolygons();
  1438. // special array of polygons
  1439. if (geom.polygons)
  1440. return geom.polygons.length;
  1441. const attr = geom.getAttribute('position');
  1442. return attr?.count ? Math.round(attr.count / 3) : 0;
  1443. }
  1444. /** @summary Returns number of faces for provided geometry
  1445. * @param {Object} geom - can be BufferGeometry, CsgGeometry or interim array of polygons
  1446. * @private */
  1447. function numGeometryVertices(geom) {
  1448. if (!geom) return 0;
  1449. if (geom instanceof CsgGeometry)
  1450. return geom.tree.numPolygons() * 3;
  1451. if (geom.polygons)
  1452. return geom.polygons.length * 4;
  1453. return geom.getAttribute('position')?.count || 0;
  1454. }
  1455. /** @summary Returns geometry bounding box
  1456. * @private */
  1457. function geomBoundingBox(geom) {
  1458. if (!geom) return null;
  1459. let polygons = null;
  1460. if (geom instanceof CsgGeometry)
  1461. polygons = geom.tree.collectPolygons();
  1462. else if (geom.polygons)
  1463. polygons = geom.polygons;
  1464. if (polygons !== null) {
  1465. const box = new THREE.Box3();
  1466. for (let n = 0; n < polygons.length; ++n) {
  1467. const polygon = polygons[n], nvert = polygon.vertices.length;
  1468. for (let k = 0; k < nvert; ++k)
  1469. box.expandByPoint(polygon.vertices[k]);
  1470. }
  1471. return box;
  1472. }
  1473. if (!geom.boundingBox)
  1474. geom.computeBoundingBox();
  1475. return geom.boundingBox.clone();
  1476. }
  1477. /** @summary Creates half-space geometry for given shape
  1478. * @desc Just big-enough triangle to make BSP calculations
  1479. * @private */
  1480. function createHalfSpace(shape, geom) {
  1481. if (!shape?.fN || !shape?.fP) return null;
  1482. const vertex = new THREE.Vector3(shape.fP[0], shape.fP[1], shape.fP[2]),
  1483. normal = new THREE.Vector3(shape.fN[0], shape.fN[1], shape.fN[2]);
  1484. normal.normalize();
  1485. let sz = 1e10;
  1486. if (geom) {
  1487. // using real size of other geometry, we probably improve precision
  1488. const box = geomBoundingBox(geom);
  1489. if (box) sz = box.getSize(new THREE.Vector3()).length() * 1000;
  1490. }
  1491. const v0 = new THREE.Vector3(-sz, -sz/2, 0),
  1492. v1 = new THREE.Vector3(0, sz, 0),
  1493. v2 = new THREE.Vector3(sz, -sz/2, 0),
  1494. v3 = new THREE.Vector3(0, 0, -sz),
  1495. geometry = new THREE.BufferGeometry(),
  1496. positions = new Float32Array([v0.x, v0.y, v0.z, v2.x, v2.y, v2.z, v1.x, v1.y, v1.z,
  1497. v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v3.x, v3.y, v3.z,
  1498. v1.x, v1.y, v1.z, v2.x, v2.y, v2.z, v3.x, v3.y, v3.z,
  1499. v2.x, v2.y, v2.z, v0.x, v0.y, v0.z, v3.x, v3.y, v3.z]);
  1500. geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  1501. geometry.computeVertexNormals();
  1502. geometry.lookAt(normal);
  1503. geometry.computeVertexNormals();
  1504. for (let k = 0; k < positions.length; k += 3) {
  1505. positions[k] += vertex.x;
  1506. positions[k+1] += vertex.y;
  1507. positions[k+2] += vertex.z;
  1508. }
  1509. return geometry;
  1510. }
  1511. /** @summary Returns number of faces for provided geometry
  1512. * @param geom - can be BufferGeometry, CsgGeometry or interim array of polygons
  1513. * @private */
  1514. function countGeometryFaces(geom) {
  1515. if (!geom) return 0;
  1516. if (geom instanceof CsgGeometry)
  1517. return geom.tree.numPolygons();
  1518. // special array of polygons
  1519. if (geom.polygons)
  1520. return geom.polygons.length;
  1521. const attr = geom.getAttribute('position');
  1522. return attr?.count ? Math.round(attr.count / 3) : 0;
  1523. }
  1524. let createGeometry = null;
  1525. /** @summary Creates geometry for composite shape
  1526. * @private */
  1527. function createComposite(shape, faces_limit) {
  1528. if (faces_limit < 0) {
  1529. return createGeometry(shape.fNode.fLeft, -1) +
  1530. createGeometry(shape.fNode.fRight, -1);
  1531. }
  1532. let geom1, geom2, return_bsp = false;
  1533. const matrix1 = createMatrix(shape.fNode.fLeftMat),
  1534. matrix2 = createMatrix(shape.fNode.fRightMat);
  1535. if (faces_limit === 0) faces_limit = 4000;
  1536. else return_bsp = true;
  1537. if (matrix1 && (matrix1.determinant() < -0.9))
  1538. geoWarn('Axis reflection in left composite shape - not supported');
  1539. if (matrix2 && (matrix2.determinant() < -0.9))
  1540. geoWarn('Axis reflections in right composite shape - not supported');
  1541. if (shape.fNode.fLeft._typename === clTGeoHalfSpace)
  1542. geom1 = createHalfSpace(shape.fNode.fLeft);
  1543. else
  1544. geom1 = createGeometry(shape.fNode.fLeft, faces_limit);
  1545. if (!geom1) return null;
  1546. let n1 = countGeometryFaces(geom1), n2 = 0;
  1547. if (geom1._exceed_limit) n1 += faces_limit;
  1548. if (n1 < faces_limit) {
  1549. if (shape.fNode.fRight._typename === clTGeoHalfSpace)
  1550. geom2 = createHalfSpace(shape.fNode.fRight, geom1);
  1551. else
  1552. geom2 = createGeometry(shape.fNode.fRight, faces_limit);
  1553. n2 = countGeometryFaces(geom2);
  1554. }
  1555. if ((n1 + n2 >= faces_limit) || !geom2) {
  1556. if (geom1.polygons)
  1557. geom1 = createBufferGeometry(geom1.polygons);
  1558. if (matrix1) geom1.applyMatrix4(matrix1);
  1559. geom1._exceed_limit = true;
  1560. return geom1;
  1561. }
  1562. let bsp1 = new CsgGeometry(geom1, matrix1, _cfg.CompressComp ? 0 : undefined);
  1563. const bsp2 = new CsgGeometry(geom2, matrix2, bsp1.maxid);
  1564. // take over maxid from both geometries
  1565. bsp1.maxid = bsp2.maxid;
  1566. switch (shape.fNode._typename) {
  1567. case 'TGeoIntersection': bsp1.direct_intersect(bsp2); break; // '*'
  1568. case 'TGeoUnion': bsp1.direct_union(bsp2); break; // '+'
  1569. case 'TGeoSubtraction': bsp1.direct_subtract(bsp2); break; // '/'
  1570. default:
  1571. geoWarn('unsupported bool operation ' + shape.fNode._typename + ', use first geom');
  1572. }
  1573. if (countGeometryFaces(bsp1) === 0) {
  1574. geoWarn('Zero faces in comp shape' +
  1575. ` left: ${shape.fNode.fLeft._typename} ${countGeometryFaces(geom1)} faces` +
  1576. ` right: ${shape.fNode.fRight._typename} ${countGeometryFaces(geom2)} faces` +
  1577. ' use first');
  1578. bsp1 = new CsgGeometry(geom1, matrix1);
  1579. }
  1580. return return_bsp ? { polygons: bsp1.toPolygons() } : bsp1.toBufferGeometry();
  1581. }
  1582. /** @summary Try to create projected geometry
  1583. * @private */
  1584. function projectGeometry(geom, matrix, projection, position, flippedMesh) {
  1585. if (!geom.boundingBox) geom.computeBoundingBox();
  1586. const box = geom.boundingBox.clone();
  1587. box.applyMatrix4(matrix);
  1588. if (!position) position = 0;
  1589. if (((box.min[projection] >= position) && (box.max[projection] >= position)) ||
  1590. ((box.min[projection] <= position) && (box.max[projection] <= position)))
  1591. return null; // not interesting
  1592. const bsp1 = new CsgGeometry(geom, matrix, 0, flippedMesh),
  1593. sizex = 2*Math.max(Math.abs(box.min.x), Math.abs(box.max.x)),
  1594. sizey = 2*Math.max(Math.abs(box.min.y), Math.abs(box.max.y)),
  1595. sizez = 2*Math.max(Math.abs(box.min.z), Math.abs(box.max.z));
  1596. let size = 10000;
  1597. switch (projection) {
  1598. case 'x': size = Math.max(sizey, sizez); break;
  1599. case 'y': size = Math.max(sizex, sizez); break;
  1600. case 'z': size = Math.max(sizex, sizey); break;
  1601. }
  1602. const bsp2 = createNormal(projection, position, size);
  1603. bsp1.cut_from_plane(bsp2);
  1604. return bsp2.toBufferGeometry();
  1605. }
  1606. /** @summary Creates geometry model for the provided shape
  1607. * @param {Object} shape - instance of TGeoShape object
  1608. * @param {Number} limit - defines return value, see details
  1609. * @desc
  1610. * - if limit === 0 (or undefined) returns BufferGeometry
  1611. * - if limit < 0 just returns estimated number of faces
  1612. * - if limit > 0 return list of CsgPolygons (used only for composite shapes)
  1613. * @private */
  1614. createGeometry = function(shape, limit) {
  1615. if (limit === undefined) limit = 0;
  1616. try {
  1617. switch (shape._typename) {
  1618. case clTGeoBBox: return createCubeBuffer(shape, limit);
  1619. case clTGeoPara: return createParaBuffer(shape, limit);
  1620. case clTGeoTrd1:
  1621. case clTGeoTrd2: return createTrapezoidBuffer(shape, limit);
  1622. case clTGeoArb8:
  1623. case clTGeoTrap:
  1624. case clTGeoGtra: return createArb8Buffer(shape, limit);
  1625. case clTGeoSphere: return createSphereBuffer(shape, limit);
  1626. case clTGeoCone:
  1627. case clTGeoConeSeg:
  1628. case clTGeoTube:
  1629. case clTGeoTubeSeg:
  1630. case clTGeoCtub: return createTubeBuffer(shape, limit);
  1631. case clTGeoEltu: return createEltuBuffer(shape, limit);
  1632. case clTGeoTorus: return createTorusBuffer(shape, limit);
  1633. case clTGeoPcon:
  1634. case clTGeoPgon: return createPolygonBuffer(shape, limit);
  1635. case clTGeoXtru: return createXtruBuffer(shape, limit);
  1636. case clTGeoParaboloid: return createParaboloidBuffer(shape, limit);
  1637. case clTGeoHype: return createHypeBuffer(shape, limit);
  1638. case 'TGeoTessellated': return createTessellatedBuffer(shape, limit);
  1639. case clTGeoCompositeShape: return createComposite(shape, limit);
  1640. case clTGeoShapeAssembly: break;
  1641. case clTGeoScaledShape: {
  1642. const res = createGeometry(shape.fShape, limit);
  1643. if (shape.fScale && (limit >= 0) && isFunc(res?.scale))
  1644. res.scale(shape.fScale.fScale[0], shape.fScale.fScale[1], shape.fScale.fScale[2]);
  1645. return res;
  1646. }
  1647. case clTGeoHalfSpace:
  1648. if (limit < 0)
  1649. return 1; // half space if just plane used in composite
  1650. // eslint-disable-next-line no-fallthrough
  1651. default:
  1652. geoWarn(`unsupported shape type ${shape._typename}`);
  1653. }
  1654. } catch (e) {
  1655. let place = '';
  1656. if (e.stack !== undefined) {
  1657. place = e.stack.split('\n')[0];
  1658. if (place.indexOf(e.message) >= 0) place = e.stack.split('\n')[1];
  1659. else place = 'at: ' + place;
  1660. }
  1661. geoWarn(`${shape._typename} err: ${e.message} ${place}`);
  1662. }
  1663. return limit < 0 ? 0 : null;
  1664. };
  1665. /** @summary Create single shape from EVE7 render date
  1666. * @private */
  1667. function makeEveGeometry(rd) {
  1668. let off = 0;
  1669. if (rd.sz[0]) {
  1670. rd.vtxBuff = new Float32Array(rd.raw.buffer, off, rd.sz[0]);
  1671. off += rd.sz[0]*4;
  1672. }
  1673. if (rd.sz[1]) {
  1674. // normals were not used
  1675. // rd.nrmBuff = new Float32Array(rd.raw.buffer, off, rd.sz[1]);
  1676. off += rd.sz[1]*4;
  1677. }
  1678. if (rd.sz[2]) {
  1679. // these are special values in the buffer begin
  1680. rd.prefixBuf = new Uint32Array(rd.raw.buffer, off, 2);
  1681. off += 2*4;
  1682. rd.idxBuff = new Uint32Array(rd.raw.buffer, off, rd.sz[2]-2);
  1683. // off += (rd.sz[2]-2)*4;
  1684. }
  1685. const GL_TRIANGLES = 4; // same as in EVE7
  1686. if (rd.prefixBuf[0] !== GL_TRIANGLES)
  1687. throw Error('Expect triangles first.');
  1688. const nVert = 3 * rd.prefixBuf[1]; // number of vertices to draw
  1689. if (rd.idxBuff.length !== nVert)
  1690. throw Error('Expect single list of triangles in index buffer.');
  1691. const body = new THREE.BufferGeometry();
  1692. body.setAttribute('position', new THREE.BufferAttribute(rd.vtxBuff, 3));
  1693. body.setIndex(new THREE.BufferAttribute(rd.idxBuff, 1));
  1694. body.computeVertexNormals();
  1695. return body;
  1696. }
  1697. /** @summary Create single shape from geometry viewer render date
  1698. * @private */
  1699. function makeViewerGeometry(rd) {
  1700. const vtxBuff = new Float32Array(rd.raw.buffer, 0, rd.raw.buffer.byteLength/4),
  1701. body = new THREE.BufferGeometry();
  1702. body.setAttribute('position', new THREE.BufferAttribute(vtxBuff, 3));
  1703. body.setIndex(new THREE.BufferAttribute(new Uint32Array(rd.idx), 1));
  1704. body.computeVertexNormals();
  1705. return body;
  1706. }
  1707. /** @summary Create single shape from provided raw data from web viewer.
  1708. * @desc If nsegm changed, shape will be recreated
  1709. * @private */
  1710. function createServerGeometry(rd, nsegm) {
  1711. if (rd.server_shape && ((rd.nsegm === nsegm) || !rd.shape))
  1712. return rd.server_shape;
  1713. rd.nsegm = nsegm;
  1714. let geom;
  1715. if (rd.shape) {
  1716. // case when TGeoShape provided as is
  1717. geom = createGeometry(rd.shape);
  1718. } else {
  1719. if (!rd.raw?.buffer) {
  1720. console.error('No raw data at all');
  1721. return null;
  1722. }
  1723. geom = rd.sz ? makeEveGeometry(rd) : makeViewerGeometry(rd);
  1724. }
  1725. // shape handle is similar to created in TGeoPainter
  1726. return {
  1727. _typename: '$$Shape$$', // indicate that shape can be used as is
  1728. ready: true,
  1729. geom,
  1730. nfaces: numGeometryFaces(geom)
  1731. };
  1732. }
  1733. /** @summary Provides info about geo object, used for tooltip info
  1734. * @param {Object} obj - any kind of TGeo-related object like shape or node or volume
  1735. * @private */
  1736. function provideObjectInfo(obj) {
  1737. let info = [], shape = null;
  1738. if (obj.fVolume !== undefined)
  1739. shape = obj.fVolume.fShape;
  1740. else if (obj.fShape !== undefined)
  1741. shape = obj.fShape;
  1742. else if ((obj.fShapeBits !== undefined) && (obj.fShapeId !== undefined))
  1743. shape = obj;
  1744. if (!shape) {
  1745. info.push(obj._typename);
  1746. return info;
  1747. }
  1748. const sz = Math.max(shape.fDX, shape.fDY, shape.fDZ),
  1749. useexp = (sz > 1e7) || (sz < 1e-7),
  1750. conv = (v) => {
  1751. if (v === undefined) return '???';
  1752. if ((v === Math.round(v) && v < 1e7)) return Math.round(v);
  1753. return useexp ? v.toExponential(4) : v.toPrecision(7);
  1754. };
  1755. info.push(shape._typename);
  1756. info.push(`DX=${conv(shape.fDX)} DY=${conv(shape.fDY)} DZ=${conv(shape.fDZ)}`);
  1757. switch (shape._typename) {
  1758. case clTGeoBBox: break;
  1759. case clTGeoPara: info.push(`Alpha=${shape.fAlpha} Phi=${shape.fPhi} Theta=${shape.fTheta}`); break;
  1760. case clTGeoTrd2: info.push(`Dy1=${conv(shape.fDy1)} Dy2=${conv(shape.fDy1)}`); // no break
  1761. // eslint-disable-next-line no-fallthrough
  1762. case clTGeoTrd1: info.push(`Dx1=${conv(shape.fDx1)} Dx2=${conv(shape.fDx1)}`); break;
  1763. case clTGeoArb8: break;
  1764. case clTGeoTrap: break;
  1765. case clTGeoGtra: break;
  1766. case clTGeoSphere:
  1767. info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`,
  1768. `Phi1=${shape.fPhi1} Phi2=${shape.fPhi2}`,
  1769. `Theta1=${shape.fTheta1} Theta2=${shape.fTheta2}`);
  1770. break;
  1771. case clTGeoConeSeg:
  1772. info.push(`Phi1=${shape.fPhi1} Phi2=${shape.fPhi2}`);
  1773. // eslint-disable-next-line no-fallthrough
  1774. case clTGeoCone:
  1775. info.push(`Rmin1=${conv(shape.fRmin1)} Rmax1=${conv(shape.fRmax1)}`,
  1776. `Rmin2=${conv(shape.fRmin2)} Rmax2=${conv(shape.fRmax2)}`);
  1777. break;
  1778. case clTGeoCtub:
  1779. case clTGeoTubeSeg:
  1780. info.push(`Phi1=${shape.fPhi1} Phi2=${shape.fPhi2}`);
  1781. // eslint-disable-next-line no-fallthrough
  1782. case clTGeoEltu:
  1783. case clTGeoTube:
  1784. info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`);
  1785. break;
  1786. case clTGeoTorus:
  1787. info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`,
  1788. `Phi1=${shape.fPhi1} Dphi=${shape.fDphi}`);
  1789. break;
  1790. case clTGeoPcon:
  1791. case clTGeoPgon: break;
  1792. case clTGeoXtru: break;
  1793. case clTGeoParaboloid:
  1794. info.push(`Rlo=${conv(shape.fRlo)} Rhi=${conv(shape.fRhi)}`,
  1795. `A=${conv(shape.fA)} B=${conv(shape.fB)}`);
  1796. break;
  1797. case clTGeoHype:
  1798. info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`,
  1799. `StIn=${conv(shape.fStIn)} StOut=${conv(shape.fStOut)}`);
  1800. break;
  1801. case clTGeoCompositeShape: break;
  1802. case clTGeoShapeAssembly: break;
  1803. case clTGeoScaledShape:
  1804. info = provideObjectInfo(shape.fShape);
  1805. if (shape.fScale)
  1806. info.unshift(`Scale X=${shape.fScale.fScale[0]} Y=${shape.fScale.fScale[1]} Z=${shape.fScale.fScale[2]}`);
  1807. break;
  1808. }
  1809. return info;
  1810. }
  1811. /** @summary Creates projection matrix for the camera
  1812. * @private */
  1813. function createProjectionMatrix(camera) {
  1814. const cameraProjectionMatrix = new THREE.Matrix4();
  1815. camera.updateMatrixWorld();
  1816. camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
  1817. cameraProjectionMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
  1818. return cameraProjectionMatrix;
  1819. }
  1820. /** @summary Creates frustum
  1821. * @private */
  1822. function createFrustum(source) {
  1823. if (!source) return null;
  1824. if (source instanceof THREE.PerspectiveCamera)
  1825. source = createProjectionMatrix(source);
  1826. const frustum = new THREE.Frustum();
  1827. frustum.setFromProjectionMatrix(source);
  1828. frustum.corners = new Float32Array([
  1829. 1, 1, 1,
  1830. 1, 1, -1,
  1831. 1, -1, 1,
  1832. 1, -1, -1,
  1833. -1, 1, 1,
  1834. -1, 1, -1,
  1835. -1, -1, 1,
  1836. -1, -1, -1,
  1837. 0, 0, 0 // also check center of the shape
  1838. ]);
  1839. frustum.test = new THREE.Vector3(0, 0, 0);
  1840. frustum.CheckShape = function(matrix, shape) {
  1841. const pnt = this.test, len = this.corners.length, corners = this.corners;
  1842. for (let i = 0; i < len; i+=3) {
  1843. pnt.x = corners[i] * shape.fDX;
  1844. pnt.y = corners[i+1] * shape.fDY;
  1845. pnt.z = corners[i+2] * shape.fDZ;
  1846. if (this.containsPoint(pnt.applyMatrix4(matrix))) return true;
  1847. }
  1848. return false;
  1849. };
  1850. frustum.CheckBox = function(box) {
  1851. const pnt = this.test;
  1852. let cnt = 0;
  1853. pnt.set(box.min.x, box.min.y, box.min.z);
  1854. if (this.containsPoint(pnt)) cnt++;
  1855. pnt.set(box.min.x, box.min.y, box.max.z);
  1856. if (this.containsPoint(pnt)) cnt++;
  1857. pnt.set(box.min.x, box.max.y, box.min.z);
  1858. if (this.containsPoint(pnt)) cnt++;
  1859. pnt.set(box.min.x, box.max.y, box.max.z);
  1860. if (this.containsPoint(pnt)) cnt++;
  1861. pnt.set(box.max.x, box.max.y, box.max.z);
  1862. if (this.containsPoint(pnt)) cnt++;
  1863. pnt.set(box.max.x, box.min.y, box.max.z);
  1864. if (this.containsPoint(pnt)) cnt++;
  1865. pnt.set(box.max.x, box.max.y, box.min.z);
  1866. if (this.containsPoint(pnt)) cnt++;
  1867. pnt.set(box.max.x, box.max.y, box.max.z);
  1868. if (this.containsPoint(pnt)) cnt++;
  1869. return cnt > 5; // only if 6 edges and more are seen, we think that box is fully visible
  1870. };
  1871. return frustum;
  1872. }
  1873. /** @summary Create node material
  1874. * @private */
  1875. function createMaterial(cfg, args0) {
  1876. if (!cfg) cfg = { material_kind: 'lambert' };
  1877. const args = Object.assign({}, args0);
  1878. if (args.opacity === undefined)
  1879. args.opacity = 1;
  1880. if (cfg.transparency)
  1881. args.opacity = Math.min(1 - cfg.transparency, args.opacity);
  1882. args.wireframe = cfg.wireframe ?? false;
  1883. if (!args.color) args.color = 'red';
  1884. args.side = THREE.FrontSide;
  1885. args.transparent = args.opacity < 1;
  1886. args.depthWrite = args.opactity === 1;
  1887. let material;
  1888. if (cfg.material_kind === 'basic')
  1889. material = new THREE.MeshBasicMaterial(args);
  1890. else if (cfg.material_kind === 'depth') {
  1891. delete args.color;
  1892. material = new THREE.MeshDepthMaterial(args);
  1893. } else if (cfg.material_kind === 'toon')
  1894. material = new THREE.MeshToonMaterial(args);
  1895. else if (cfg.material_kind === 'matcap') {
  1896. delete args.wireframe;
  1897. material = new THREE.MeshMatcapMaterial(args);
  1898. } else if (cfg.material_kind === 'standard') {
  1899. args.metalness = cfg.metalness ?? 0.5;
  1900. args.roughness = cfg.roughness ?? 0.1;
  1901. material = new THREE.MeshStandardMaterial(args);
  1902. } else if (cfg.material_kind === 'normal') {
  1903. delete args.color;
  1904. material = new THREE.MeshNormalMaterial(args);
  1905. } else if (cfg.material_kind === 'physical') {
  1906. args.metalness = cfg.metalness ?? 0.5;
  1907. args.roughness = cfg.roughness ?? 0.1;
  1908. args.reflectivity = cfg.reflectivity ?? 0.5;
  1909. args.emissive = args.color;
  1910. material = new THREE.MeshPhysicalMaterial(args);
  1911. } else if (cfg.material_kind === 'phong') {
  1912. args.shininess = cfg.shininess ?? 0.9;
  1913. material = new THREE.MeshPhongMaterial(args);
  1914. } else {
  1915. args.vertexColors = false;
  1916. material = new THREE.MeshLambertMaterial(args);
  1917. }
  1918. if ((material.flatShading !== undefined) && (cfg.flatShading !== undefined))
  1919. material.flatShading = cfg.flatShading;
  1920. material.inherentOpacity = args0.opacity ?? 1;
  1921. material.inherentArgs = args0;
  1922. return material;
  1923. }
  1924. /** @summary Compares two stacks.
  1925. * @return {Number} 0 if same, -1 when stack1 < stack2, +1 when stack1 > stack2
  1926. * @private */
  1927. function compare_stacks(stack1, stack2) {
  1928. if (stack1 === stack2)
  1929. return 0;
  1930. const len1 = stack1?.length ?? 0,
  1931. len2 = stack2?.length ?? 0,
  1932. len = (len1 < len2) ? len1 : len2;
  1933. let indx = 0;
  1934. while (indx < len) {
  1935. if (stack1[indx] < stack2[indx])
  1936. return -1;
  1937. if (stack1[indx] > stack2[indx])
  1938. return 1;
  1939. ++indx;
  1940. }
  1941. return (len1 < len2) ? -1 : ((len1 > len2) ? 1 : 0);
  1942. }
  1943. /** @summary Checks if two stack arrays are identical
  1944. * @private */
  1945. function isSameStack(stack1, stack2) {
  1946. if (!stack1 || !stack2) return false;
  1947. if (stack1 === stack2) return true;
  1948. if (stack1.length !== stack2.length) return false;
  1949. for (let k = 0; k < stack1.length; ++k)
  1950. if (stack1[k] !== stack2[k]) return false;
  1951. return true;
  1952. }
  1953. function createFlippedGeom(geom) {
  1954. let pos = geom.getAttribute('position').array,
  1955. norm = geom.getAttribute('normal').array;
  1956. const index = geom.getIndex();
  1957. if (index) {
  1958. // we need to unfold all points to
  1959. const arr = index.array,
  1960. i0 = geom.drawRange.start;
  1961. let ilen = geom.drawRange.count;
  1962. if (i0 + ilen > arr.length) ilen = arr.length - i0;
  1963. const dpos = new Float32Array(ilen*3), dnorm = new Float32Array(ilen*3);
  1964. for (let ii = 0; ii < ilen; ++ii) {
  1965. const k = arr[i0 + ii];
  1966. if ((k < 0) || (k*3 >= pos.length))
  1967. console.log(`strange index ${k*3} totallen = ${pos.length}`);
  1968. dpos[ii*3] = pos[k*3];
  1969. dpos[ii*3+1] = pos[k*3+1];
  1970. dpos[ii*3+2] = pos[k*3+2];
  1971. dnorm[ii*3] = norm[k*3];
  1972. dnorm[ii*3+1] = norm[k*3+1];
  1973. dnorm[ii*3+2] = norm[k*3+2];
  1974. }
  1975. pos = dpos; norm = dnorm;
  1976. }
  1977. const len = pos.length,
  1978. newpos = new Float32Array(len),
  1979. newnorm = new Float32Array(len);
  1980. // we should swap second and third point in each face
  1981. for (let n = 0, shift = 0; n < len; n += 3) {
  1982. newpos[n] = pos[n+shift];
  1983. newpos[n+1] = pos[n+1+shift];
  1984. newpos[n+2] = -pos[n+2+shift];
  1985. newnorm[n] = norm[n+shift];
  1986. newnorm[n+1] = norm[n+1+shift];
  1987. newnorm[n+2] = -norm[n+2+shift];
  1988. shift+=3; if (shift===6) shift=-3; // values 0,3,-3
  1989. }
  1990. const geomZ = new THREE.BufferGeometry();
  1991. geomZ.setAttribute('position', new THREE.BufferAttribute(newpos, 3));
  1992. geomZ.setAttribute('normal', new THREE.BufferAttribute(newnorm, 3));
  1993. return geomZ;
  1994. }
  1995. /** @summary Create flipped mesh for the shape
  1996. * @desc When transformation matrix includes one or several inversion of axis,
  1997. * one should inverse geometry object, otherwise three.js cannot correctly draw it
  1998. * @param {Object} shape - TGeoShape object
  1999. * @param {Object} material - material
  2000. * @private */
  2001. function createFlippedMesh(shape, material) {
  2002. if (shape.geomZ === undefined)
  2003. shape.geomZ = createFlippedGeom(shape.geom);
  2004. const mesh = new THREE.Mesh(shape.geomZ, material);
  2005. mesh.scale.copy(new THREE.Vector3(1, 1, -1));
  2006. mesh.updateMatrix();
  2007. mesh._flippedMesh = true;
  2008. return mesh;
  2009. }
  2010. /**
  2011. * @summary class for working with cloned nodes
  2012. *
  2013. * @private
  2014. */
  2015. class ClonedNodes {
  2016. /** @summary Constructor */
  2017. constructor(obj, clones) {
  2018. this.toplevel = true; // indicate if object creates top-level structure with Nodes and Volumes folder
  2019. this.name_prefix = ''; // name prefix used for nodes names
  2020. this.maxdepth = 1; // maximal hierarchy depth, required for transparency
  2021. this.vislevel = 4; // maximal depth of nodes visibility aka gGeoManager->SetVisLevel, same default
  2022. this.maxnodes = 10000; // maximal number of visible nodes aka gGeoManager->fMaxVisNodes
  2023. if (obj) {
  2024. if (obj.$geoh) this.toplevel = false;
  2025. this.createClones(obj);
  2026. } else if (clones)
  2027. this.nodes = clones;
  2028. }
  2029. /** @summary Set maximal depth for nodes visibility */
  2030. setVisLevel(lvl) {
  2031. this.vislevel = lvl && Number.isInteger(lvl) ? lvl : 4;
  2032. }
  2033. /** @summary Returns maximal depth for nodes visibility */
  2034. getVisLevel() {
  2035. return this.vislevel;
  2036. }
  2037. /** @summary Set maximal number of visible nodes
  2038. * @desc By default 10000 nodes will be visualized */
  2039. setMaxVisNodes(v, more) {
  2040. this.maxnodes = (v === Infinity) ? 1e9 : (Number.isFinite(v) ? v : 10000);
  2041. if (more && Number.isFinite(more))
  2042. this.maxnodes *= more;
  2043. }
  2044. /** @summary Returns configured maximal number of visible nodes */
  2045. getMaxVisNodes() {
  2046. return this.maxnodes;
  2047. }
  2048. /** @summary Set geo painter configuration - used for material creation */
  2049. setConfig(cfg) {
  2050. this._cfg = cfg;
  2051. }
  2052. /** @summary Insert node into existing array */
  2053. updateNode(node) {
  2054. if (node && Number.isInteger(node.id) && (node.id < this.nodes.length))
  2055. this.nodes[node.id] = node;
  2056. }
  2057. /** @summary Returns TGeoShape for element with given indx */
  2058. getNodeShape(indx) {
  2059. if (!this.origin || !this.nodes) return null;
  2060. const obj = this.origin[indx], clone = this.nodes[indx];
  2061. if (!obj || !clone) return null;
  2062. if (clone.kind === kindGeo) {
  2063. if (obj.fVolume) return obj.fVolume.fShape;
  2064. } else
  2065. return obj.fShape;
  2066. return null;
  2067. }
  2068. /** @summary function to cleanup as much as possible structures
  2069. * @desc Provided parameters drawnodes and drawshapes are arrays created during building of geometry */
  2070. cleanup(drawnodes, drawshapes) {
  2071. if (drawnodes) {
  2072. for (let n = 0; n < drawnodes.length; ++n) {
  2073. delete drawnodes[n].stack;
  2074. drawnodes[n] = undefined;
  2075. }
  2076. }
  2077. if (drawshapes) {
  2078. for (let n = 0; n < drawshapes.length; ++n) {
  2079. delete drawshapes[n].geom;
  2080. drawshapes[n] = undefined;
  2081. }
  2082. }
  2083. if (this.nodes) {
  2084. for (let n = 0; n < this.nodes.length; ++n) {
  2085. if (this.nodes[n])
  2086. delete this.nodes[n].chlds;
  2087. }
  2088. }
  2089. delete this.nodes;
  2090. delete this.origin;
  2091. delete this.sortmap;
  2092. }
  2093. /** @summary Create complete description for provided Geo object */
  2094. createClones(obj, sublevel, kind) {
  2095. if (!sublevel) {
  2096. if (obj?._typename === '$$Shape$$')
  2097. return this.createClonesForShape(obj);
  2098. this.origin = [];
  2099. sublevel = 1;
  2100. kind = getNodeKind(obj);
  2101. }
  2102. if ((kind < 0) || !obj || ('_refid' in obj)) return;
  2103. obj._refid = this.origin.length;
  2104. this.origin.push(obj);
  2105. if (sublevel > this.maxdepth) this.maxdepth = sublevel;
  2106. let chlds;
  2107. if (kind === kindGeo)
  2108. chlds = obj.fVolume?.fNodes?.arr || null;
  2109. else
  2110. chlds = obj.fElements?.arr || null;
  2111. if (chlds !== null) {
  2112. checkDuplicates(obj, chlds);
  2113. for (let i = 0; i < chlds.length; ++i)
  2114. this.createClones(chlds[i], sublevel + 1, kind);
  2115. }
  2116. if (sublevel > 1)
  2117. return;
  2118. this.nodes = [];
  2119. const sortarr = [];
  2120. // first create nodes objects
  2121. for (let id = 0; id < this.origin.length; ++id) {
  2122. // let obj = this.origin[id];
  2123. const node = { id, kind, vol: 0, nfaces: 0 };
  2124. this.nodes.push(node);
  2125. sortarr.push(node); // array use to produce sortmap
  2126. }
  2127. // than fill children lists
  2128. for (let n = 0; n < this.origin.length; ++n) {
  2129. const obj2 = this.origin[n],
  2130. clone = this.nodes[n],
  2131. shape = kind === kindEve ? obj2.fShape : obj2.fVolume.fShape,
  2132. chlds2 = kind === kindEve ? obj2.fElements?.arr : obj2.fVolume.fNodes?.arr,
  2133. matrix = getNodeMatrix(kind, obj2);
  2134. if (matrix) {
  2135. clone.matrix = matrix.elements; // take only matrix elements, matrix will be constructed in worker
  2136. if (clone.matrix && (clone.matrix[0] === 1)) {
  2137. let issimple = true;
  2138. for (let k = 1; (k < clone.matrix.length) && issimple; ++k)
  2139. issimple = (clone.matrix[k] === ((k === 5) || (k === 10) || (k === 15) ? 1 : 0));
  2140. if (issimple) delete clone.matrix;
  2141. }
  2142. if (clone.matrix && (kind === kindEve))
  2143. clone.abs_matrix = true;
  2144. }
  2145. if (shape) {
  2146. clone.fDX = shape.fDX;
  2147. clone.fDY = shape.fDY;
  2148. clone.fDZ = shape.fDZ;
  2149. clone.vol = Math.sqrt(shape.fDX**2 + shape.fDY**2 + shape.fDZ**2);
  2150. if (shape.$nfaces === undefined)
  2151. shape.$nfaces = createGeometry(shape, -1);
  2152. clone.nfaces = shape.$nfaces;
  2153. if (clone.nfaces <= 0)
  2154. clone.vol = 0;
  2155. }
  2156. if (chlds2) {
  2157. // in cloned object children is only list of ids
  2158. clone.chlds = new Array(chlds2.length);
  2159. for (let k = 0; k < chlds2.length; ++k)
  2160. clone.chlds[k] = chlds2[k]._refid;
  2161. }
  2162. }
  2163. // remove _refid identifiers from original objects
  2164. for (let n = 0; n < this.origin.length; ++n)
  2165. delete this.origin[n]._refid;
  2166. // do sorting once
  2167. sortarr.sort((a, b) => b.vol - a.vol);
  2168. // remember sort map and also sortid
  2169. this.sortmap = new Array(this.nodes.length);
  2170. for (let n = 0; n < this.nodes.length; ++n) {
  2171. this.sortmap[n] = sortarr[n].id;
  2172. sortarr[n].sortid = n;
  2173. }
  2174. }
  2175. /** @summary Create elementary item with single already existing shape
  2176. * @desc used by details view of geometry shape */
  2177. createClonesForShape(obj) {
  2178. this.origin = [];
  2179. // indicate that just plain shape is used
  2180. this.plain_shape = obj;
  2181. this.nodes = [{
  2182. id: 0, sortid: 0, kind: kindShape,
  2183. name: 'Shape',
  2184. nfaces: obj.nfaces,
  2185. fDX: 1, fDY: 1, fDZ: 1, vol: 1,
  2186. vis: true
  2187. }];
  2188. }
  2189. /** @summary Count all visible nodes */
  2190. countVisibles() {
  2191. const len = this.nodes?.length || 0;
  2192. let cnt = 0;
  2193. for (let k = 0; k < len; ++k)
  2194. if (this.nodes[k].vis) cnt++;
  2195. return cnt;
  2196. }
  2197. /** @summary Mark visible nodes.
  2198. * @desc Set only basic flags, actual visibility depends from hierarchy */
  2199. markVisibles(on_screen, copy_bits, hide_top_volume) {
  2200. if (this.plain_shape)
  2201. return 1;
  2202. if (!this.origin || !this.nodes)
  2203. return 0;
  2204. let res = 0;
  2205. for (let n = 0; n < this.nodes.length; ++n) {
  2206. const clone = this.nodes[n], obj = this.origin[n];
  2207. clone.vis = 0; // 1 - only with last level
  2208. delete clone.nochlds;
  2209. if (clone.kind === kindGeo) {
  2210. if (obj.fVolume) {
  2211. if (on_screen) {
  2212. // on screen bits used always, childs always checked
  2213. clone.vis = testGeoBit(obj.fVolume, geoBITS.kVisOnScreen) ? 99 : 0;
  2214. if ((n === 0) && clone.vis && hide_top_volume) clone.vis = 0;
  2215. if (copy_bits) {
  2216. setGeoBit(obj.fVolume, geoBITS.kVisNone, false);
  2217. setGeoBit(obj.fVolume, geoBITS.kVisThis, (clone.vis > 0));
  2218. setGeoBit(obj.fVolume, geoBITS.kVisDaughters, true);
  2219. setGeoBit(obj, geoBITS.kVisDaughters, true);
  2220. }
  2221. } else {
  2222. clone.vis = !testGeoBit(obj.fVolume, geoBITS.kVisNone) && testGeoBit(obj.fVolume, geoBITS.kVisThis) ? 99 : 0;
  2223. if (!testGeoBit(obj, geoBITS.kVisDaughters) || !testGeoBit(obj.fVolume, geoBITS.kVisDaughters))
  2224. clone.nochlds = true;
  2225. // node with childs only shown in case if it is last level in hierarchy
  2226. if ((clone.vis > 0) && clone.chlds && !clone.nochlds)
  2227. clone.vis = 1;
  2228. // special handling for top node
  2229. if (n === 0) {
  2230. if (hide_top_volume) clone.vis = 0;
  2231. delete clone.nochlds;
  2232. }
  2233. }
  2234. }
  2235. } else {
  2236. clone.vis = obj.fRnrSelf ? 99 : 0;
  2237. // when the only node is selected, draw it
  2238. if ((n === 0) && (this.nodes.length === 1)) clone.vis = 99;
  2239. this.vislevel = 9999; // automatically take all volumes
  2240. }
  2241. // shape with zero volume or without faces will not be observed
  2242. if ((clone.vol <= 0) || (clone.nfaces <= 0)) clone.vis = 0;
  2243. if (clone.vis) res++;
  2244. }
  2245. return res;
  2246. }
  2247. /** @summary After visibility flags is set, produce id shifts for all nodes as it would be maximum level */
  2248. produceIdShifts() {
  2249. for (let k = 0; k < this.nodes.length; ++k)
  2250. this.nodes[k].idshift = -1;
  2251. function scan_func(nodes, node) {
  2252. if (node.idshift < 0) {
  2253. node.idshift = 0;
  2254. if (node.chlds) {
  2255. for (let k = 0; k<node.chlds.length; ++k)
  2256. node.idshift += scan_func(nodes, nodes[node.chlds[k]]);
  2257. }
  2258. }
  2259. return node.idshift + 1;
  2260. }
  2261. scan_func(this.nodes, this.nodes[0]);
  2262. }
  2263. /** @summary Extract only visibility flags
  2264. * @desc Used to transfer them to the worker */
  2265. getVisibleFlags() {
  2266. const res = new Array(this.nodes.length);
  2267. for (let n=0; n<this.nodes.length; ++n)
  2268. res[n] = { vis: this.nodes[n].vis, nochlds: this.nodes[n].nochlds };
  2269. return res;
  2270. }
  2271. /** @summary Assign only visibility flags, extracted with getVisibleFlags */
  2272. setVisibleFlags(flags) {
  2273. if (!this.nodes || !flags || !flags.length !== this.nodes.length)
  2274. return 0;
  2275. let res = 0;
  2276. for (let n = 0; n < this.nodes.length; ++n) {
  2277. const clone = this.nodes[n];
  2278. clone.vis = flags[n].vis;
  2279. clone.nochlds = flags[n].nochlds;
  2280. if (clone.vis) res++;
  2281. }
  2282. return res;
  2283. }
  2284. /** @summary Set visibility flag for physical node
  2285. * @desc Trying to reimplement functionality in the RGeomViewer */
  2286. setPhysNodeVisibility(stack, on) {
  2287. let do_clear = false;
  2288. if (on === 'clearall') {
  2289. delete this.fVisibility;
  2290. return;
  2291. } else if (on === 'clear') {
  2292. do_clear = true;
  2293. if (!this.fVisibility) return;
  2294. } else
  2295. on = Boolean(on);
  2296. if (!stack)
  2297. return;
  2298. if (!this.fVisibility)
  2299. this.fVisibility = [];
  2300. for (let indx = 0; indx < this.fVisibility.length; ++indx) {
  2301. const item = this.fVisibility[indx],
  2302. res = compare_stacks(item.stack, stack);
  2303. if (res === 0) {
  2304. if (do_clear) {
  2305. this.fVisibility.splice(indx, 1);
  2306. if (this.fVisibility.length === 0)
  2307. delete this.fVisibility;
  2308. } else
  2309. item.visible = on;
  2310. return;
  2311. }
  2312. if (res > 0) {
  2313. if (!do_clear)
  2314. this.fVisibility.splice(indx, 0, { visible: on, stack });
  2315. return;
  2316. }
  2317. }
  2318. if (!do_clear)
  2319. this.fVisibility.push({ visible: on, stack });
  2320. }
  2321. /** @summary Get visibility item for physical node */
  2322. getPhysNodeVisibility(stack) {
  2323. if (!stack || !this.fVisibility)
  2324. return null;
  2325. for (let indx = 0; indx < this.fVisibility.length; ++indx) {
  2326. const item = this.fVisibility[indx],
  2327. res = compare_stacks(item.stack, stack);
  2328. if (res === 0)
  2329. return item;
  2330. if (res > 0)
  2331. return null;
  2332. }
  2333. return null;
  2334. }
  2335. /** @summary Scan visible nodes in hierarchy, starting from nodeid
  2336. * @desc Each entry in hierarchy get its unique id, which is not changed with visibility flags */
  2337. scanVisible(arg, vislvl) {
  2338. if (!this.nodes) return 0;
  2339. if (vislvl === undefined) {
  2340. if (!arg) arg = {};
  2341. vislvl = arg.vislvl || this.vislevel || 4; // default 3 in ROOT
  2342. if (vislvl > 88) vislvl = 88;
  2343. arg.stack = new Array(100); // current stack
  2344. arg.nodeid = 0;
  2345. arg.counter = 0; // sequence ID of the node, used to identify it later
  2346. arg.last = 0;
  2347. arg.copyStack = function(factor) {
  2348. const entry = { nodeid: this.nodeid, seqid: this.counter, stack: new Array(this.last) };
  2349. if (factor) entry.factor = factor; // factor used to indicate importance of entry, will be built as first
  2350. for (let n = 0; n < this.last; ++n)
  2351. entry.stack[n] = this.stack[n+1]; // copy stack
  2352. return entry;
  2353. };
  2354. if (arg.domatrix) {
  2355. arg.matrices = [];
  2356. arg.mpool = [new THREE.Matrix4()]; // pool of Matrix objects to avoid permanent creation
  2357. arg.getmatrix = function() { return this.matrices[this.last]; };
  2358. }
  2359. if (this.fVisibility?.length) {
  2360. arg.vindx = 0;
  2361. arg.varray = this.fVisibility;
  2362. arg.vstack = arg.varray[arg.vindx].stack;
  2363. arg.testPhysVis = function() {
  2364. if (!this.vstack || (this.vstack?.length !== this.last))
  2365. return undefined;
  2366. for (let n = 0; n < this.last; ++n) {
  2367. if (this.vstack[n] !== this.stack[n+1])
  2368. return undefined;
  2369. }
  2370. const res = this.varray[this.vindx++].visible;
  2371. this.vstack = this.vindx < this.varray.length ? this.varray[this.vindx].stack : null;
  2372. return res;
  2373. };
  2374. }
  2375. }
  2376. const node = this.nodes[arg.nodeid];
  2377. let res = 0;
  2378. if (arg.domatrix) {
  2379. if (!arg.mpool[arg.last+1])
  2380. arg.mpool[arg.last+1] = new THREE.Matrix4();
  2381. const prnt = (arg.last > 0) ? arg.matrices[arg.last-1] : new THREE.Matrix4();
  2382. if (node.matrix) {
  2383. arg.matrices[arg.last] = arg.mpool[arg.last].fromArray(prnt.elements);
  2384. arg.matrices[arg.last].multiply(arg.mpool[arg.last+1].fromArray(node.matrix));
  2385. } else
  2386. arg.matrices[arg.last] = prnt;
  2387. }
  2388. let node_vis = node.vis, node_nochlds = node.nochlds;
  2389. if ((arg.nodeid === 0) && arg.main_visible)
  2390. node_vis = vislvl + 1;
  2391. else if (arg.testPhysVis) {
  2392. const res2 = arg.testPhysVis();
  2393. if (res2 !== undefined) {
  2394. node_vis = res2 && !node.chlds ? vislvl + 1 : 0;
  2395. node_nochlds = !res2;
  2396. }
  2397. }
  2398. if (node_nochlds)
  2399. vislvl = 0;
  2400. if (node_vis > vislvl) {
  2401. if (!arg.func || arg.func(node))
  2402. res++;
  2403. }
  2404. arg.counter++;
  2405. if ((vislvl > 0) && node.chlds) {
  2406. arg.last++;
  2407. for (let i = 0; i < node.chlds.length; ++i) {
  2408. arg.nodeid = node.chlds[i];
  2409. arg.stack[arg.last] = i; // in the stack one store index of child, it is path in the hierarchy
  2410. res += this.scanVisible(arg, vislvl-1);
  2411. }
  2412. arg.last--;
  2413. } else
  2414. arg.counter += (node.idshift || 0);
  2415. if (arg.last === 0) {
  2416. delete arg.last;
  2417. delete arg.stack;
  2418. delete arg.copyStack;
  2419. delete arg.counter;
  2420. delete arg.matrices;
  2421. delete arg.mpool;
  2422. delete arg.getmatrix;
  2423. delete arg.vindx;
  2424. delete arg.varray;
  2425. delete arg.vstack;
  2426. delete arg.testPhysVis;
  2427. }
  2428. return res;
  2429. }
  2430. /** @summary Return node name with given id.
  2431. * @desc Either original object or description is used */
  2432. getNodeName(nodeid) {
  2433. if (this.origin) {
  2434. const obj = this.origin[nodeid];
  2435. return obj ? getObjectName(obj) : '';
  2436. }
  2437. const node = this.nodes[nodeid];
  2438. return node ? node.name : '';
  2439. }
  2440. /** @summary Returns description for provided stack
  2441. * @desc If specified, absolute matrix is also calculated */
  2442. resolveStack(stack, withmatrix) {
  2443. const res = { id: 0, obj: null, node: this.nodes[0], name: this.name_prefix || '' };
  2444. if (withmatrix) {
  2445. res.matrix = new THREE.Matrix4();
  2446. if (res.node.matrix) res.matrix.fromArray(res.node.matrix);
  2447. }
  2448. if (this.origin)
  2449. res.obj = this.origin[0];
  2450. // if (!res.name)
  2451. // res.name = this.getNodeName(0);
  2452. if (stack) {
  2453. for (let lvl = 0; lvl < stack.length; ++lvl) {
  2454. res.id = res.node.chlds[stack[lvl]];
  2455. res.node = this.nodes[res.id];
  2456. if (this.origin)
  2457. res.obj = this.origin[res.id];
  2458. const subname = this.getNodeName(res.id);
  2459. if (subname) {
  2460. if (res.name) res.name += '/';
  2461. res.name += subname;
  2462. }
  2463. if (withmatrix && res.node.matrix)
  2464. res.matrix.multiply(new THREE.Matrix4().fromArray(res.node.matrix));
  2465. }
  2466. }
  2467. return res;
  2468. }
  2469. /** @summary Provide stack name
  2470. * @desc Stack name includes full path to the physical node which is identified by stack */
  2471. getStackName(stack) {
  2472. return this.resolveStack(stack).name;
  2473. }
  2474. /** @summary Create stack array based on nodes ids array.
  2475. * @desc Ids list should correspond to existing nodes hierarchy */
  2476. buildStackByIds(ids) {
  2477. if (!ids) return null;
  2478. if (ids[0] !== 0) {
  2479. console.error('wrong ids - first should be 0');
  2480. return null;
  2481. }
  2482. let node = this.nodes[0];
  2483. const stack = [];
  2484. for (let k = 1; k < ids.length; ++k) {
  2485. const nodeid = ids[k];
  2486. if (!node) return null;
  2487. const chindx = node.chlds.indexOf(nodeid);
  2488. if (chindx < 0) {
  2489. console.error(`wrong nodes ids ${ids[k]} is not child of ${ids[k-1]}`);
  2490. return null;
  2491. }
  2492. stack.push(chindx);
  2493. node = this.nodes[nodeid];
  2494. }
  2495. return stack;
  2496. }
  2497. /** @summary Returns ids array which correspond to the stack */
  2498. buildIdsByStack(stack) {
  2499. if (!stack) return null;
  2500. let node = this.nodes[0];
  2501. const ids = [0];
  2502. for (let k = 0; k < stack.length; ++k) {
  2503. const id = node.chlds[stack[k]];
  2504. ids.push(id);
  2505. node = this.nodes[id];
  2506. }
  2507. return ids;
  2508. }
  2509. /** @summary Returns node id by stack */
  2510. getNodeIdByStack(stack) {
  2511. if (!stack || !this.nodes)
  2512. return -1;
  2513. let node = this.nodes[0], id = 0;
  2514. for (let k = 0; k < stack.length; ++k) {
  2515. id = node.chlds[stack[k]];
  2516. node = this.nodes[id];
  2517. }
  2518. return id;
  2519. }
  2520. /** @summary Returns true if stack includes at any place provided nodeid */
  2521. isIdInStack(nodeid, stack) {
  2522. if (!nodeid)
  2523. return true;
  2524. let node = this.nodes[0];
  2525. for (let lvl = 0; lvl < stack.length; ++lvl) {
  2526. const id = node.chlds[stack[lvl]];
  2527. if (id === nodeid)
  2528. return true;
  2529. node = this.nodes[id];
  2530. }
  2531. return false;
  2532. }
  2533. /** @summary Find stack by name which include names of all parents */
  2534. findStackByName(fullname) {
  2535. const names = fullname.split('/'), stack = [];
  2536. let currid = 0;
  2537. if (this.getNodeName(currid) !== names[0]) return null;
  2538. for (let n = 1; n < names.length; ++n) {
  2539. const node = this.nodes[currid];
  2540. if (!node.chlds) return null;
  2541. for (let k = 0; k < node.chlds.length; ++k) {
  2542. const chldid = node.chlds[k];
  2543. if (this.getNodeName(chldid) === names[n]) {
  2544. stack.push(k);
  2545. currid = chldid;
  2546. break;
  2547. }
  2548. }
  2549. // no new entry - not found stack
  2550. if (stack.length === n - 1) return null;
  2551. }
  2552. return stack;
  2553. }
  2554. /** @summary Set usage of default ROOT colors */
  2555. setDefaultColors(on) {
  2556. this.use_dflt_colors = on;
  2557. if (this.use_dflt_colors && !this.dflt_table) {
  2558. const dflt = { kWhite: 0, kBlack: 1, kGray: 920,
  2559. kRed: 632, kGreen: 416, kBlue: 600, kYellow: 400, kMagenta: 616, kCyan: 432,
  2560. kOrange: 800, kSpring: 820, kTeal: 840, kAzure: 860, kViolet: 880, kPink: 900 },
  2561. nmax = 110, col = [];
  2562. for (let i=0; i<nmax; i++) col.push(dflt.kGray);
  2563. // here we should create a new TColor with the same rgb as in the default
  2564. // ROOT colors used below
  2565. col[3] = dflt.kYellow-10;
  2566. col[4] = col[5] = dflt.kGreen-10;
  2567. col[6] = col[7] = dflt.kBlue-7;
  2568. col[8] = col[9] = dflt.kMagenta-3;
  2569. col[10] = col[11] = dflt.kRed-10;
  2570. col[12] = dflt.kGray+1;
  2571. col[13] = dflt.kBlue-10;
  2572. col[14] = dflt.kOrange+7;
  2573. col[16] = dflt.kYellow+1;
  2574. col[20] = dflt.kYellow-10;
  2575. col[24] = col[25] = col[26] = dflt.kBlue-8;
  2576. col[29] = dflt.kOrange+9;
  2577. col[79] = dflt.kOrange-2;
  2578. this.dflt_table = col;
  2579. }
  2580. }
  2581. /** @summary Provide different properties of draw entry nodeid
  2582. * @desc Only if node visible, material will be created */
  2583. getDrawEntryProperties(entry, root_colors) {
  2584. const clone = this.nodes[entry.nodeid], visible = true;
  2585. if (clone.kind === kindShape) {
  2586. const prop = { name: clone.name, nname: clone.name, shape: null, material: null, chlds: null },
  2587. opacity = entry.opacity || 1, col = entry.color || '#0000FF';
  2588. prop.fillcolor = new THREE.Color(col[0] === '#' ? col : `rgb(${col})`);
  2589. prop.material = createMaterial(this._cfg, { opacity, color: prop.fillcolor });
  2590. return prop;
  2591. }
  2592. if (!this.origin) {
  2593. console.error(`origin not there - kind ${clone.kind} id ${entry.nodeid}`);
  2594. return null;
  2595. }
  2596. const node = this.origin[entry.nodeid];
  2597. if (clone.kind === kindEve) {
  2598. // special handling for EVE nodes
  2599. const prop = { name: getObjectName(node), nname: getObjectName(node), shape: node.fShape, material: null, chlds: null };
  2600. if (node.fElements !== null) prop.chlds = node.fElements.arr;
  2601. if (visible) {
  2602. const opacity = Math.min(1, node.fRGBA[3]);
  2603. prop.fillcolor = new THREE.Color(node.fRGBA[0], node.fRGBA[1], node.fRGBA[2]);
  2604. prop.material = createMaterial(this._cfg, { opacity, color: prop.fillcolor });
  2605. }
  2606. return prop;
  2607. }
  2608. const volume = node.fVolume,
  2609. prop = { name: getObjectName(volume), nname: getObjectName(node), volume, shape: volume.fShape, material: null,
  2610. chlds: volume.fNodes?.arr, linewidth: volume.fLineWidth };
  2611. if (visible) {
  2612. // TODO: maybe correctly extract ROOT colors here?
  2613. let opacity = 1.0;
  2614. if (!root_colors) root_colors = ['white', 'black', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan'];
  2615. if (entry.custom_color)
  2616. prop.fillcolor = entry.custom_color;
  2617. else if ((volume.fFillColor > 1) && (volume.fLineColor === 1))
  2618. prop.fillcolor = root_colors[volume.fFillColor];
  2619. else if (volume.fLineColor >= 0)
  2620. prop.fillcolor = root_colors[volume.fLineColor];
  2621. const mat = volume.fMedium?.fMaterial;
  2622. if (mat) {
  2623. const fillstyle = mat.fFillStyle;
  2624. let transparency = (fillstyle >= 3000 && fillstyle <= 3100) ? fillstyle - 3000 : 0;
  2625. if (this.use_dflt_colors) {
  2626. const matZ = Math.round(mat.fZ), icol = this.dflt_table[matZ];
  2627. prop.fillcolor = root_colors[icol];
  2628. if (mat.fDensity < 0.1) transparency = 60;
  2629. }
  2630. if (transparency > 0)
  2631. opacity = (100 - transparency) / 100;
  2632. if (prop.fillcolor === undefined)
  2633. prop.fillcolor = root_colors[mat.fFillColor];
  2634. }
  2635. if (prop.fillcolor === undefined)
  2636. prop.fillcolor = 'lightgrey';
  2637. prop.material = createMaterial(this._cfg, { opacity, color: prop.fillcolor });
  2638. }
  2639. return prop;
  2640. }
  2641. /** @summary Creates hierarchy of Object3D for given stack entry
  2642. * @desc Such hierarchy repeats hierarchy of TGeoNodes and set matrix for the objects drawing
  2643. * also set renderOrder, required to handle transparency */
  2644. createObject3D(stack, toplevel, options) {
  2645. let node = this.nodes[0], three_prnt = toplevel, draw_depth = 0;
  2646. const force = isObject(options) || (options === 'force');
  2647. for (let lvl = 0; lvl <= stack.length; ++lvl) {
  2648. const nchld = (lvl > 0) ? stack[lvl-1] : 0,
  2649. // extract current node
  2650. child = (lvl > 0) ? this.nodes[node.chlds[nchld]] : node;
  2651. if (!child) {
  2652. console.error(`Wrong stack ${JSON.stringify(stack)} for nodes at level ${lvl}, node.id ${node.id}, numnodes ${this.nodes.length}, nchld ${nchld}, numchilds ${node.chlds.length}, chldid ${node.chlds[nchld]}`);
  2653. return null;
  2654. }
  2655. node = child;
  2656. let obj3d;
  2657. if (three_prnt.children) {
  2658. for (let i = 0; i < three_prnt.children.length; ++i) {
  2659. if (three_prnt.children[i].nchld === nchld) {
  2660. obj3d = three_prnt.children[i];
  2661. break;
  2662. }
  2663. }
  2664. }
  2665. if (obj3d) {
  2666. three_prnt = obj3d;
  2667. if (obj3d.$jsroot_drawable) draw_depth++;
  2668. continue;
  2669. }
  2670. if (!force) return null;
  2671. obj3d = new THREE.Object3D();
  2672. if (this._cfg?.set_names)
  2673. obj3d.name = this.getNodeName(node.id);
  2674. if (this._cfg?.set_origin && this.origin)
  2675. obj3d.userData = this.origin[node.id];
  2676. if (node.abs_matrix) {
  2677. obj3d.absMatrix = new THREE.Matrix4();
  2678. obj3d.absMatrix.fromArray(node.matrix);
  2679. } else if (node.matrix) {
  2680. obj3d.matrix.fromArray(node.matrix);
  2681. obj3d.matrix.decompose(obj3d.position, obj3d.quaternion, obj3d.scale);
  2682. }
  2683. // this.accountNodes(obj3d);
  2684. obj3d.nchld = nchld; // mark index to find it again later
  2685. // add the mesh to the scene
  2686. three_prnt.add(obj3d);
  2687. // this is only for debugging - test inversion of whole geometry
  2688. if ((lvl === 0) && isObject(options) && options.scale) {
  2689. if ((options.scale.x < 0) || (options.scale.y < 0) || (options.scale.z < 0)) {
  2690. obj3d.scale.copy(options.scale);
  2691. obj3d.updateMatrix();
  2692. }
  2693. }
  2694. obj3d.updateMatrixWorld();
  2695. three_prnt = obj3d;
  2696. }
  2697. if ((options === 'mesh') || (options === 'delete_mesh')) {
  2698. let mesh = null;
  2699. if (three_prnt) {
  2700. for (let n = 0; (n < three_prnt.children.length) && !mesh; ++n) {
  2701. const chld = three_prnt.children[n];
  2702. if ((chld.type === 'Mesh') && (chld.nchld === undefined)) mesh = chld;
  2703. }
  2704. }
  2705. if ((options === 'mesh') || !mesh) return mesh;
  2706. const res = three_prnt;
  2707. while (mesh && (mesh !== toplevel)) {
  2708. three_prnt = mesh.parent;
  2709. three_prnt.remove(mesh);
  2710. mesh = (three_prnt.children.length === 0) ? three_prnt : null;
  2711. }
  2712. return res;
  2713. }
  2714. if (three_prnt) {
  2715. three_prnt.$jsroot_drawable = true;
  2716. three_prnt.$jsroot_depth = draw_depth;
  2717. }
  2718. return three_prnt;
  2719. }
  2720. /** @summary Create mesh for single physical node */
  2721. createEntryMesh(ctrl, toplevel, entry, shape, colors) {
  2722. if (!shape || !shape.ready)
  2723. return null;
  2724. entry.done = true; // mark entry is created
  2725. shape.used = true; // indicate that shape was used in building
  2726. if (!shape.geom || !shape.nfaces) {
  2727. // node is visible, but shape does not created
  2728. this.createObject3D(entry.stack, toplevel, 'delete_mesh');
  2729. return null;
  2730. }
  2731. const prop = this.getDrawEntryProperties(entry, colors),
  2732. obj3d = this.createObject3D(entry.stack, toplevel, ctrl),
  2733. matrix = obj3d.absMatrix || obj3d.matrixWorld;
  2734. prop.material.wireframe = ctrl.wireframe;
  2735. prop.material.side = ctrl.doubleside ? THREE.DoubleSide : THREE.FrontSide;
  2736. let mesh;
  2737. if (matrix.determinant() > -0.9)
  2738. mesh = new THREE.Mesh(shape.geom, prop.material);
  2739. else
  2740. mesh = createFlippedMesh(shape, prop.material);
  2741. obj3d.add(mesh);
  2742. if (obj3d.absMatrix) {
  2743. mesh.matrix.copy(obj3d.absMatrix);
  2744. mesh.matrix.decompose(mesh.position, mesh.quaternion, mesh.scale);
  2745. mesh.updateMatrixWorld();
  2746. }
  2747. // keep full stack of nodes
  2748. mesh.stack = entry.stack;
  2749. mesh.renderOrder = this.maxdepth - entry.stack.length; // order of transparency handling
  2750. if (ctrl.set_names)
  2751. mesh.name = this.getNodeName(entry.nodeid);
  2752. if (ctrl.set_origin)
  2753. mesh.userData = prop.volume;
  2754. // keep hierarchy level
  2755. mesh.$jsroot_order = obj3d.$jsroot_depth;
  2756. if (ctrl.info?.num_meshes !== undefined) {
  2757. ctrl.info.num_meshes++;
  2758. ctrl.info.num_faces += shape.nfaces;
  2759. }
  2760. // set initial render order, when camera moves, one must refine it
  2761. // mesh.$jsroot_order = mesh.renderOrder =
  2762. // this._clones.maxdepth - ((obj3d.$jsroot_depth !== undefined) ? obj3d.$jsroot_depth : entry.stack.length);
  2763. return mesh;
  2764. }
  2765. /** @summary Check if instancing can be used for the nodes */
  2766. createInstancedMeshes(ctrl, toplevel, draw_nodes, build_shapes, colors) {
  2767. if (ctrl.instancing < 0)
  2768. return false;
  2769. // first delete previous data
  2770. const used_shapes = [];
  2771. let max_entries = 1;
  2772. for (let n = 0; n < draw_nodes.length; ++n) {
  2773. const entry = draw_nodes[n];
  2774. if (entry.done) continue;
  2775. // shape can be provided with entry itself
  2776. const shape = entry.server_shape || build_shapes[entry.shapeid];
  2777. if (!shape || !shape.ready) {
  2778. console.warn(`Problem with shape id ${entry.shapeid} when building`);
  2779. return false;
  2780. }
  2781. // ignore shape without geometry
  2782. if (!shape.geom || !shape.nfaces)
  2783. continue;
  2784. if (shape.instances === undefined) {
  2785. shape.instances = [];
  2786. used_shapes.push(shape);
  2787. }
  2788. const instance = shape.instances.find(i => i.nodeid === entry.nodeid);
  2789. if (instance) {
  2790. instance.entries.push(entry);
  2791. max_entries = Math.max(max_entries, instance.entries.length);
  2792. } else
  2793. shape.instances.push({ nodeid: entry.nodeid, entries: [entry] });
  2794. }
  2795. const make_sense = ctrl.instancing > 0 ? (max_entries > 2) : (draw_nodes.length > 10000) && (max_entries > 10);
  2796. if (!make_sense) {
  2797. used_shapes.forEach(shape => { delete shape.instances; });
  2798. return false;
  2799. }
  2800. used_shapes.forEach(shape => {
  2801. shape.used = true;
  2802. shape.instances.forEach(instance => {
  2803. const entry0 = instance.entries[0],
  2804. prop = this.getDrawEntryProperties(entry0, colors);
  2805. prop.material.wireframe = ctrl.wireframe;
  2806. prop.material.side = ctrl.doubleside ? THREE.DoubleSide : THREE.FrontSide;
  2807. if (instance.entries.length === 1)
  2808. this.createEntryMesh(ctrl, toplevel, entry0, shape, colors);
  2809. else {
  2810. const arr1 = [], arr2 = [], stacks1 = [], stacks2 = [], names1 = [], names2 = [];
  2811. instance.entries.forEach(entry => {
  2812. const info = this.resolveStack(entry.stack, true);
  2813. if (info.matrix.determinant() > -0.9) {
  2814. arr1.push(info.matrix);
  2815. stacks1.push(entry.stack);
  2816. names1.push(this.getNodeName(entry.nodeid));
  2817. } else {
  2818. arr2.push(info.matrix);
  2819. stacks2.push(entry.stack);
  2820. names2.push(this.getNodeName(entry.nodeid));
  2821. }
  2822. entry.done = true;
  2823. });
  2824. if (arr1.length > 0) {
  2825. const mesh1 = new THREE.InstancedMesh(shape.geom, prop.material, arr1.length);
  2826. mesh1.stacks = stacks1;
  2827. arr1.forEach((matrix, i) => mesh1.setMatrixAt(i, matrix));
  2828. toplevel.add(mesh1);
  2829. mesh1.renderOrder = 1;
  2830. if (ctrl.set_names) {
  2831. mesh1.name = names1[0];
  2832. mesh1.names = names1;
  2833. }
  2834. if (ctrl.set_origin)
  2835. mesh1.userData = prop.volume;
  2836. mesh1.$jsroot_order = 1;
  2837. ctrl.info.num_meshes++;
  2838. ctrl.info.num_faces += shape.nfaces*arr1.length;
  2839. }
  2840. if (arr2.length > 0) {
  2841. if (shape.geomZ === undefined)
  2842. shape.geomZ = createFlippedGeom(shape.geom);
  2843. const mesh2 = new THREE.InstancedMesh(shape.geomZ, prop.material, arr2.length);
  2844. mesh2.stacks = stacks2;
  2845. const m = new THREE.Matrix4().makeScale(1, 1, -1);
  2846. arr2.forEach((matrix, i) => {
  2847. mesh2.setMatrixAt(i, matrix.multiply(m));
  2848. });
  2849. mesh2._flippedMesh = true;
  2850. toplevel.add(mesh2);
  2851. mesh2.renderOrder = 1;
  2852. if (ctrl.set_names) {
  2853. mesh2.name = names2[0];
  2854. mesh2.names = names2;
  2855. }
  2856. if (ctrl.set_origin)
  2857. mesh2.userData = prop.volume;
  2858. mesh2.$jsroot_order = 1;
  2859. ctrl.info.num_meshes++;
  2860. ctrl.info.num_faces += shape.nfaces*arr2.length;
  2861. }
  2862. }
  2863. });
  2864. delete shape.instances;
  2865. });
  2866. return true;
  2867. }
  2868. /** @summary Get volume boundary */
  2869. getVolumeBoundary(viscnt, facelimit, nodeslimit) {
  2870. const result = { min: 0, max: 1, sortidcut: 0 };
  2871. if (!this.sortmap) {
  2872. console.error('sorting map do not exist');
  2873. return result;
  2874. }
  2875. let maxNode, currNode, cnt=0, facecnt=0;
  2876. for (let n = 0; (n < this.sortmap.length) && (cnt < nodeslimit) && (facecnt < facelimit); ++n) {
  2877. const id = this.sortmap[n];
  2878. if (viscnt[id] === 0) continue;
  2879. currNode = this.nodes[id];
  2880. if (!maxNode) maxNode = currNode;
  2881. cnt += viscnt[id];
  2882. facecnt += viscnt[id] * currNode.nfaces;
  2883. }
  2884. if (!currNode) {
  2885. console.error('no volumes selected');
  2886. return result;
  2887. }
  2888. result.max = maxNode.vol;
  2889. result.min = currNode.vol;
  2890. result.sortidcut = currNode.sortid; // latest node is not included
  2891. return result;
  2892. }
  2893. /** @summary Collects visible nodes, using maxlimit
  2894. * @desc One can use map to define cut based on the volume or serious of cuts */
  2895. collectVisibles(maxnumfaces, frustum) {
  2896. // in simple case shape as it is
  2897. if (this.plain_shape)
  2898. return { lst: [{ nodeid: 0, seqid: 0, stack: [], factor: 1, shapeid: 0, server_shape: this.plain_shape }], complete: true };
  2899. const arg = {
  2900. facecnt: 0,
  2901. viscnt: new Array(this.nodes.length), // counter for each node
  2902. vislvl: this.getVisLevel(),
  2903. reset() {
  2904. this.total = 0;
  2905. this.facecnt = 0;
  2906. this.viscnt.fill(0);
  2907. },
  2908. func(node) {
  2909. this.total++;
  2910. this.facecnt += node.nfaces;
  2911. this.viscnt[node.id]++;
  2912. return true;
  2913. }
  2914. };
  2915. arg.reset();
  2916. let total = this.scanVisible(arg);
  2917. if ((total === 0) && (this.nodes[0].vis < 2) && !this.nodes[0].nochlds) {
  2918. // try to draw only main node by default
  2919. arg.reset();
  2920. arg.main_visible = true;
  2921. total = this.scanVisible(arg);
  2922. }
  2923. const maxnumnodes = this.getMaxVisNodes();
  2924. if (maxnumnodes > 0) {
  2925. while ((total > maxnumnodes) && (arg.vislvl > 1)) {
  2926. arg.vislvl--;
  2927. arg.reset();
  2928. total = this.scanVisible(arg);
  2929. }
  2930. }
  2931. this.actual_level = arg.vislvl; // not used, can be shown somewhere in the gui
  2932. let minVol = 0, maxVol, camVol = -1, camFact = 10, sortidcut = this.nodes.length + 1;
  2933. if (arg.facecnt > maxnumfaces) {
  2934. const bignumfaces = maxnumfaces * (frustum ? 0.8 : 1.0),
  2935. bignumnodes = maxnumnodes * (frustum ? 0.8 : 1.0),
  2936. // define minimal volume, which always to shown
  2937. boundary = this.getVolumeBoundary(arg.viscnt, bignumfaces, bignumnodes);
  2938. minVol = boundary.min;
  2939. maxVol = boundary.max;
  2940. sortidcut = boundary.sortidcut;
  2941. if (frustum) {
  2942. arg.domatrix = true;
  2943. arg.frustum = frustum;
  2944. arg.totalcam = 0;
  2945. arg.func = function(node) {
  2946. if (node.vol <= minVol) {
  2947. // only small volumes are interesting
  2948. if (this.frustum.CheckShape(this.getmatrix(), node)) {
  2949. this.viscnt[node.id]++;
  2950. this.totalcam += node.nfaces;
  2951. }
  2952. }
  2953. return true;
  2954. };
  2955. for (let n = 0; n < arg.viscnt.length; ++n)
  2956. arg.viscnt[n] = 0;
  2957. this.scanVisible(arg);
  2958. if (arg.totalcam > maxnumfaces*0.2)
  2959. camVol = this.getVolumeBoundary(arg.viscnt, maxnumfaces*0.2, maxnumnodes*0.2).min;
  2960. else
  2961. camVol = 0;
  2962. camFact = maxVol / ((camVol > 0) ? (camVol > 0) : minVol);
  2963. }
  2964. }
  2965. arg.items = [];
  2966. arg.func = function(node) {
  2967. if (node.sortid < sortidcut)
  2968. this.items.push(this.copyStack());
  2969. else if ((camVol >= 0) && (node.vol > camVol)) {
  2970. if (this.frustum.CheckShape(this.getmatrix(), node))
  2971. this.items.push(this.copyStack(camFact));
  2972. }
  2973. return true;
  2974. };
  2975. this.scanVisible(arg);
  2976. return { lst: arg.items, complete: minVol === 0 };
  2977. }
  2978. /** @summary Merge list of drawn objects
  2979. * @desc In current list we should mark if object already exists
  2980. * from previous list we should collect objects which are not there */
  2981. mergeVisibles(current, prev) {
  2982. let indx2 = 0;
  2983. const del = [];
  2984. for (let indx1 = 0; (indx1 < current.length) && (indx2 < prev.length); ++indx1) {
  2985. while ((indx2 < prev.length) && (prev[indx2].seqid < current[indx1].seqid))
  2986. del.push(prev[indx2++]); // this entry should be removed
  2987. if ((indx2 < prev.length) && (prev[indx2].seqid === current[indx1].seqid)) {
  2988. if (prev[indx2].done) current[indx1].done = true; // copy ready flag
  2989. indx2++;
  2990. }
  2991. }
  2992. // remove rest
  2993. while (indx2 < prev.length)
  2994. del.push(prev[indx2++]);
  2995. return del;
  2996. }
  2997. /** @summary Collect all uniques shapes which should be built
  2998. * @desc Check if same shape used many times for drawing */
  2999. collectShapes(lst) {
  3000. // nothing else - just that single shape
  3001. if (this.plain_shape)
  3002. return [this.plain_shape];
  3003. const shapes = [];
  3004. for (let i = 0; i < lst.length; ++i) {
  3005. const entry = lst[i],
  3006. shape = this.getNodeShape(entry.nodeid);
  3007. if (!shape) continue; // strange, but avoid misleading
  3008. if (shape._id === undefined) {
  3009. shape._id = shapes.length;
  3010. shapes.push({ id: shape._id, shape, vol: this.nodes[entry.nodeid].vol, refcnt: 1, factor: 1, ready: false });
  3011. // shapes.push( { obj: shape, vol: this.nodes[entry.nodeid].vol });
  3012. } else
  3013. shapes[shape._id].refcnt++;
  3014. entry.shape = shapes[shape._id]; // remember shape used
  3015. // use maximal importance factor to push element to the front
  3016. if (entry.factor && (entry.factor>entry.shape.factor))
  3017. entry.shape.factor = entry.factor;
  3018. }
  3019. // now sort shapes in volume decrease order
  3020. shapes.sort((a, b) => b.vol*b.factor - a.vol*a.factor);
  3021. // now set new shape ids according to the sorted order and delete temporary field
  3022. for (let n = 0; n < shapes.length; ++n) {
  3023. const item = shapes[n];
  3024. item.id = n; // set new ID
  3025. delete item.shape._id; // remove temporary field
  3026. }
  3027. // as last action set current shape id to each entry
  3028. for (let i = 0; i < lst.length; ++i) {
  3029. const entry = lst[i];
  3030. if (entry.shape) {
  3031. entry.shapeid = entry.shape.id; // keep only id for the entry
  3032. delete entry.shape; // remove direct references
  3033. }
  3034. }
  3035. return shapes;
  3036. }
  3037. /** @summary Merge shape lists */
  3038. mergeShapesLists(oldlst, newlst) {
  3039. if (!oldlst) return newlst;
  3040. // set geometry to shape object itself
  3041. for (let n = 0; n < oldlst.length; ++n) {
  3042. const item = oldlst[n];
  3043. item.shape._geom = item.geom;
  3044. delete item.geom;
  3045. if (item.geomZ !== undefined) {
  3046. item.shape._geomZ = item.geomZ;
  3047. delete item.geomZ;
  3048. }
  3049. }
  3050. // take from shape (if match)
  3051. for (let n = 0; n < newlst.length; ++n) {
  3052. const item = newlst[n];
  3053. if (item.shape._geom !== undefined) {
  3054. item.geom = item.shape._geom;
  3055. delete item.shape._geom;
  3056. }
  3057. if (item.shape._geomZ !== undefined) {
  3058. item.geomZ = item.shape._geomZ;
  3059. delete item.shape._geomZ;
  3060. }
  3061. }
  3062. // now delete all unused geometries
  3063. for (let n = 0; n < oldlst.length; ++n) {
  3064. const item = oldlst[n];
  3065. delete item.shape._geom;
  3066. delete item.shape._geomZ;
  3067. }
  3068. return newlst;
  3069. }
  3070. /** @summary Build shapes */
  3071. buildShapes(lst, limit, timelimit) {
  3072. let created = 0;
  3073. const tm1 = new Date().getTime(),
  3074. res = { done: false, shapes: 0, faces: 0, notusedshapes: 0 };
  3075. for (let n = 0; n < lst.length; ++n) {
  3076. const item = lst[n];
  3077. // if enough faces are produced, nothing else is required
  3078. if (res.done) { item.ready = true; continue; }
  3079. if (!item.ready) {
  3080. item._typename = '$$Shape$$'; // let reuse item for direct drawing
  3081. item.ready = true;
  3082. if (item.geom === undefined) {
  3083. item.geom = createGeometry(item.shape);
  3084. if (item.geom) created++; // indicate that at least one shape was created
  3085. }
  3086. item.nfaces = countGeometryFaces(item.geom);
  3087. }
  3088. res.shapes++;
  3089. if (!item.used) res.notusedshapes++;
  3090. res.faces += item.nfaces * item.refcnt;
  3091. if (res.faces >= limit)
  3092. res.done = true;
  3093. else if ((created > 0.01*lst.length) && (timelimit !== undefined)) {
  3094. const tm2 = new Date().getTime();
  3095. if (tm2 - tm1 > timelimit) return res;
  3096. }
  3097. }
  3098. res.done = true;
  3099. return res;
  3100. }
  3101. /** @summary Format REveGeomNode data to be able use it in list of clones
  3102. * @private */
  3103. static formatServerElement(elem) {
  3104. elem.kind = 2; // special element for geom viewer, used in TGeoPainter
  3105. elem.vis = 2; // visibility is alwys on
  3106. const m = elem.matr;
  3107. delete elem.matr;
  3108. if (!m?.length) return elem;
  3109. if (m.length === 16)
  3110. elem.matrix = m;
  3111. else {
  3112. const nm = elem.matrix = new Array(16);
  3113. nm.fill(0);
  3114. nm[0] = nm[5] = nm[10] = nm[15] = 1;
  3115. if (m.length === 3) {
  3116. // translation matrix
  3117. nm[12] = m[0]; nm[13] = m[1]; nm[14] = m[2];
  3118. } else if (m.length === 4) {
  3119. // scale matrix
  3120. nm[0] = m[0]; nm[5] = m[1]; nm[10] = m[2]; nm[15] = m[3];
  3121. } else if (m.length === 9) {
  3122. // rotation matrix
  3123. nm[0] = m[0]; nm[4] = m[1]; nm[8] = m[2];
  3124. nm[1] = m[3]; nm[5] = m[4]; nm[9] = m[5];
  3125. nm[2] = m[6]; nm[6] = m[7]; nm[10] = m[8];
  3126. } else
  3127. console.error(`wrong number of elements ${m.length} in the matrix`);
  3128. }
  3129. return elem;
  3130. }
  3131. } // class ClonedNodes
  3132. /** @summary extract code of Box3.expandByObject
  3133. * @desc Major difference - do not traverse hierarchy, support InstancedMesh
  3134. * @private */
  3135. function getBoundingBox(node, box3, local_coordinates) {
  3136. if (!node?.geometry) return box3;
  3137. if (!box3) box3 = new THREE.Box3().makeEmpty();
  3138. if (node.isInstancedMesh) {
  3139. const m = new THREE.Matrix4(), b = new THREE.Box3().makeEmpty();
  3140. node.geometry.computeBoundingBox();
  3141. for (let i = 0; i < node.count; i++) {
  3142. node.getMatrixAt(i, m);
  3143. b.copy(node.geometry.boundingBox).applyMatrix4(m);
  3144. box3.union(b);
  3145. }
  3146. return box3;
  3147. }
  3148. if (!local_coordinates) node.updateWorldMatrix(false, false);
  3149. const v1 = new THREE.Vector3(), attribute = node.geometry.attributes?.position;
  3150. if (attribute !== undefined) {
  3151. for (let i = 0, l = attribute.count; i < l; i++) {
  3152. // v1.fromAttribute( attribute, i ).applyMatrix4( node.matrixWorld );
  3153. v1.fromBufferAttribute(attribute, i);
  3154. if (!local_coordinates) v1.applyMatrix4(node.matrixWorld);
  3155. box3.expandByPoint(v1);
  3156. }
  3157. }
  3158. return box3;
  3159. }
  3160. /** @summary Cleanup shape entity
  3161. * @private */
  3162. function cleanupShape(shape) {
  3163. if (!shape) return;
  3164. if (isFunc(shape.geom?.dispose))
  3165. shape.geom.dispose();
  3166. if (isFunc(shape.geomZ?.dispose))
  3167. shape.geomZ.dispose();
  3168. delete shape.geom;
  3169. delete shape.geomZ;
  3170. }
  3171. /** @summary Set rendering order for created hierarchy
  3172. * @desc depending from provided method sort differently objects
  3173. * @param toplevel - top element
  3174. * @param origin - camera position used to provide sorting
  3175. * @param method - name of sorting method like 'pnt', 'ray', 'size', 'dflt' */
  3176. function produceRenderOrder(toplevel, origin, method, clones) {
  3177. const raycast = new THREE.Raycaster();
  3178. function setdefaults(top) {
  3179. if (!top) return;
  3180. top.traverse(obj => {
  3181. obj.renderOrder = obj.defaultOrder || 0;
  3182. if (obj.material) obj.material.depthWrite = true; // by default depthWriting enabled
  3183. });
  3184. }
  3185. function traverse(obj, lvl, arr) {
  3186. // traverse hierarchy and extract all children of given level
  3187. // if (obj.$jsroot_depth === undefined) return;
  3188. if (!obj.children) return;
  3189. for (let k = 0; k < obj.children.length; ++k) {
  3190. const chld = obj.children[k];
  3191. if (chld.$jsroot_order === lvl) {
  3192. if (chld.material) {
  3193. if (chld.material.transparent) {
  3194. chld.material.depthWrite = false; // disable depth writing for transparent
  3195. arr.push(chld);
  3196. } else
  3197. setdefaults(chld);
  3198. }
  3199. } else if ((obj.$jsroot_depth === undefined) || (obj.$jsroot_depth < lvl))
  3200. traverse(chld, lvl, arr);
  3201. }
  3202. }
  3203. function sort(arr, minorder, maxorder) {
  3204. // resort meshes using ray caster and camera position
  3205. // idea to identify meshes which are in front or behind
  3206. if (arr.length > 1000) {
  3207. // too many of them, just set basic level and exit
  3208. for (let i = 0; i < arr.length; ++i)
  3209. arr[i].renderOrder = (minorder + maxorder)/2;
  3210. return false;
  3211. }
  3212. const tmp_vect = new THREE.Vector3();
  3213. // first calculate distance to the camera
  3214. // it gives preliminary order of volumes
  3215. for (let i = 0; i < arr.length; ++i) {
  3216. const mesh = arr[i];
  3217. let box3 = mesh.$jsroot_box3;
  3218. if (!box3)
  3219. mesh.$jsroot_box3 = box3 = getBoundingBox(mesh);
  3220. if (method === 'size') {
  3221. const sz = box3.getSize(new THREE.Vector3());
  3222. mesh.$jsroot_distance = sz.x*sz.y*sz.z;
  3223. continue;
  3224. }
  3225. if (method === 'pnt') {
  3226. mesh.$jsroot_distance = origin.distanceTo(box3.getCenter(tmp_vect));
  3227. continue;
  3228. }
  3229. let dist = Math.min(origin.distanceTo(box3.min), origin.distanceTo(box3.max));
  3230. const pnt = new THREE.Vector3(box3.min.x, box3.min.y, box3.max.z);
  3231. dist = Math.min(dist, origin.distanceTo(pnt));
  3232. pnt.set(box3.min.x, box3.max.y, box3.min.z);
  3233. dist = Math.min(dist, origin.distanceTo(pnt));
  3234. pnt.set(box3.max.x, box3.min.y, box3.min.z);
  3235. dist = Math.min(dist, origin.distanceTo(pnt));
  3236. pnt.set(box3.max.x, box3.max.y, box3.min.z);
  3237. dist = Math.min(dist, origin.distanceTo(pnt));
  3238. pnt.set(box3.max.x, box3.min.y, box3.max.z);
  3239. dist = Math.min(dist, origin.distanceTo(pnt));
  3240. pnt.set(box3.min.x, box3.max.y, box3.max.z);
  3241. dist = Math.min(dist, origin.distanceTo(pnt));
  3242. mesh.$jsroot_distance = dist;
  3243. }
  3244. arr.sort((a, b) => a.$jsroot_distance - b.$jsroot_distance);
  3245. const resort = new Array(arr.length);
  3246. for (let i = 0; i < arr.length; ++i) {
  3247. arr[i].$jsroot_index = i;
  3248. resort[i] = arr[i];
  3249. }
  3250. if (method === 'ray') {
  3251. for (let i=arr.length - 1; i >= 0; --i) {
  3252. const mesh = arr[i], box3 = mesh.$jsroot_box3;
  3253. let intersects, direction = box3.getCenter(tmp_vect);
  3254. for (let ntry = 0; ntry < 2; ++ntry) {
  3255. direction.sub(origin).normalize();
  3256. raycast.set(origin, direction);
  3257. intersects = raycast.intersectObjects(arr, false) || []; // only plain array
  3258. const unique = [];
  3259. for (let k1 = 0; k1 < intersects.length; ++k1) {
  3260. if (unique.indexOf(intersects[k1].object) < 0)
  3261. unique.push(intersects[k1].object);
  3262. // if (intersects[k1].object === mesh) break; // trace until object itself
  3263. }
  3264. intersects = unique;
  3265. if ((intersects.indexOf(mesh) < 0) && (ntry > 0))
  3266. console.log(`MISS ${clones?.resolveStack(mesh.stack)?.name}`);
  3267. if ((intersects.indexOf(mesh) >= 0) || (ntry > 0)) break;
  3268. const pos = mesh.geometry.attributes.position.array;
  3269. direction = new THREE.Vector3((pos[0]+pos[3]+pos[6])/3, (pos[1]+pos[4]+pos[7])/3, (pos[2]+pos[5]+pos[8])/3);
  3270. direction.applyMatrix4(mesh.matrixWorld);
  3271. }
  3272. // now push first object in intersects to the front
  3273. for (let k1 = 0; k1 < intersects.length - 1; ++k1) {
  3274. const mesh1 = intersects[k1], mesh2 = intersects[k1+1],
  3275. i1 = mesh1.$jsroot_index, i2 = mesh2.$jsroot_index;
  3276. if (i1 < i2) continue;
  3277. for (let ii = i2; ii < i1; ++ii) {
  3278. resort[ii] = resort[ii+1];
  3279. resort[ii].$jsroot_index = ii;
  3280. }
  3281. resort[i1] = mesh2;
  3282. mesh2.$jsroot_index = i1;
  3283. }
  3284. }
  3285. }
  3286. for (let i = 0; i < resort.length; ++i) {
  3287. resort[i].renderOrder = Math.round(maxorder - (i+1) / (resort.length + 1) * (maxorder - minorder));
  3288. delete resort[i].$jsroot_index;
  3289. delete resort[i].$jsroot_distance;
  3290. }
  3291. return true;
  3292. }
  3293. function process(obj, lvl, minorder, maxorder) {
  3294. const arr = [];
  3295. let did_sort = false;
  3296. traverse(obj, lvl, arr);
  3297. if (!arr.length) return;
  3298. if (minorder === maxorder) {
  3299. for (let k = 0; k < arr.length; ++k)
  3300. arr[k].renderOrder = minorder;
  3301. } else {
  3302. did_sort = sort(arr, minorder, maxorder);
  3303. if (!did_sort) minorder = maxorder = (minorder + maxorder) / 2;
  3304. }
  3305. for (let k = 0; k < arr.length; ++k) {
  3306. const next = arr[k].parent;
  3307. let min = minorder, max = maxorder;
  3308. if (did_sort) {
  3309. max = arr[k].renderOrder;
  3310. min = max - (maxorder - minorder) / (arr.length + 2);
  3311. }
  3312. process(next, lvl+1, min, max);
  3313. }
  3314. }
  3315. if (!method || (method === 'dflt'))
  3316. setdefaults(toplevel);
  3317. else
  3318. process(toplevel, 0, 1, 1000000);
  3319. }
  3320. /** @summary provide icon name for the shape
  3321. * @private */
  3322. function getShapeIcon(shape) {
  3323. switch (shape._typename) {
  3324. case clTGeoArb8: return 'img_geoarb8';
  3325. case clTGeoCone: return 'img_geocone';
  3326. case clTGeoConeSeg: return 'img_geoconeseg';
  3327. case clTGeoCompositeShape: return 'img_geocomposite';
  3328. case clTGeoTube: return 'img_geotube';
  3329. case clTGeoTubeSeg: return 'img_geotubeseg';
  3330. case clTGeoPara: return 'img_geopara';
  3331. case clTGeoParaboloid: return 'img_geoparab';
  3332. case clTGeoPcon: return 'img_geopcon';
  3333. case clTGeoPgon: return 'img_geopgon';
  3334. case clTGeoShapeAssembly: return 'img_geoassembly';
  3335. case clTGeoSphere: return 'img_geosphere';
  3336. case clTGeoTorus: return 'img_geotorus';
  3337. case clTGeoTrd1: return 'img_geotrd1';
  3338. case clTGeoTrd2: return 'img_geotrd2';
  3339. case clTGeoXtru: return 'img_geoxtru';
  3340. case clTGeoTrap: return 'img_geotrap';
  3341. case clTGeoGtra: return 'img_geogtra';
  3342. case clTGeoEltu: return 'img_geoeltu';
  3343. case clTGeoHype: return 'img_geohype';
  3344. case clTGeoCtub: return 'img_geoctub';
  3345. }
  3346. return 'img_geotube';
  3347. }
  3348. export { kindGeo, kindEve, kindShape,
  3349. clTGeoBBox, clTGeoCompositeShape,
  3350. geoCfg, geoBITS, ClonedNodes, isSameStack, checkDuplicates, getObjectName, testGeoBit, setGeoBit, toggleGeoBit,
  3351. setInvisibleAll, countNumShapes, getNodeKind, produceRenderOrder, createFlippedGeom, createFlippedMesh, cleanupShape,
  3352. createGeometry, numGeometryFaces, numGeometryVertices, createServerGeometry, createMaterial,
  3353. projectGeometry, countGeometryFaces, createFrustum, createProjectionMatrix, getBoundingBox, provideObjectInfo, getShapeIcon };