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