otsdaq_utilities  v2_05_02_indev
JSRootGeoPainter.js
1 
4 (function( factory ) {
5  if ( typeof define === "function" && define.amd ) {
6  // AMD. Register as an anonymous module.
7  define( [ 'd3', 'JSRootPainter', 'JSRoot3DPainter', 'ThreeCSG' ], factory );
8  } else {
9 
10  if (typeof JSROOT == 'undefined')
11  throw new Error('JSROOT is not defined', 'JSRootGeoPainter.js');
12 
13  if (typeof JSROOT.Painter != 'object')
14  throw new Error('JSROOT.Painter is not defined', 'JSRootGeoPainter.js');
15 
16  if (typeof d3 == 'undefined')
17  throw new Error('d3 is not defined', 'JSRootGeoPainter.js');
18 
19  if (typeof THREE == 'undefined')
20  throw new Error('THREE is not defined', 'JSRootGeoPainter.js');
21 
22  factory( d3, JSROOT);
23  }
24 } (function( d3, JSROOT ) {
25 
26  if ( typeof define === "function" && define.amd )
27  JSROOT.loadScript('$$$style/JSRootGeoPainter.css');
28 
29  // === functions to create THREE.Geometry for TGeo shapes ========================
30 
31  JSROOT.GEO = {};
32 
33  JSROOT.GEO.createCube = function( shape ) {
34 
35  // instead of BoxGeometry create all vertices and faces ourself
36  // reduce number of allocated objects
37 
38  //return new THREE.BoxGeometry( 2*shape.fDX, 2*shape.fDY, 2*shape.fDZ );
39 
40  var geom = new THREE.Geometry();
41 
42  geom.vertices.push( new THREE.Vector3( shape.fDX, shape.fDY, shape.fDZ ) );
43  geom.vertices.push( new THREE.Vector3( shape.fDX, shape.fDY, -shape.fDZ ) );
44  geom.vertices.push( new THREE.Vector3( shape.fDX, -shape.fDY, shape.fDZ ) );
45  geom.vertices.push( new THREE.Vector3( shape.fDX, -shape.fDY, -shape.fDZ ) );
46  geom.vertices.push( new THREE.Vector3(-shape.fDX, shape.fDY, -shape.fDZ ) );
47  geom.vertices.push( new THREE.Vector3(-shape.fDX, shape.fDY, shape.fDZ ) );
48  geom.vertices.push( new THREE.Vector3(-shape.fDX, -shape.fDY, -shape.fDZ ) );
49  geom.vertices.push( new THREE.Vector3(-shape.fDX, -shape.fDY, shape.fDZ ) );
50 
51  var indicies = [0,2,1, 2,3,1, 4,6,5, 6,7,5, 4,5,1, 5,0,1, 7,6,2, 6,3,2, 5,7,0, 7,2,0, 1,3,4, 3,6,4];
52 
53  // normals for each pair of faces
54  var normals = [ 1,0,0, -1,0,0, 0,1,0, 0,-1,0, 0,0,1, 0,0,-1 ];
55 
56  var color = new THREE.Color();
57  var norm = null;
58  for (var n=0; n < indicies.length; n+=3) {
59  if (n % 6 === 0) norm = new THREE.Vector3(normals[n/2], normals[n/2+1], normals[n/2+2]);
60  var face = new THREE.Face3( indicies[n], indicies[n+1], indicies[n+2], norm, color, 0);
61  geom.faces.push(face);
62  }
63 
64  return geom;
65 
66  }
67 
68  JSROOT.GEO.createPara = function( shape ) {
69 
70  var txy = shape.fTxy, txz = shape.fTxz, tyz = shape.fTyz;
71 
72  var verticesOfShape = [
73  -shape.fZ*txz-txy*shape.fY-shape.fX, -shape.fY-shape.fZ*tyz, -shape.fZ,
74  -shape.fZ*txz+txy*shape.fY-shape.fX, shape.fY-shape.fZ*tyz, -shape.fZ,
75  -shape.fZ*txz+txy*shape.fY+shape.fX, shape.fY-shape.fZ*tyz, -shape.fZ,
76  -shape.fZ*txz-txy*shape.fY+shape.fX, -shape.fY-shape.fZ*tyz, -shape.fZ,
77  shape.fZ*txz-txy*shape.fY-shape.fX, -shape.fY+shape.fZ*tyz, shape.fZ,
78  shape.fZ*txz+txy*shape.fY-shape.fX, shape.fY+shape.fZ*tyz, shape.fZ,
79  shape.fZ*txz+txy*shape.fY+shape.fX, shape.fY+shape.fZ*tyz, shape.fZ,
80  shape.fZ*txz-txy*shape.fY+shape.fX, -shape.fY+shape.fZ*tyz, shape.fZ ];
81 
82  var indicesOfFaces = [ 4,6,5, 4,7,6, 0,3,7, 7,4,0,
83  4,5,1, 1,0,4, 6,2,1, 1,5,6,
84  7,3,2, 2,6,7, 1,2,3, 3,0,1 ];
85 
86  var geom = new THREE.Geometry();
87 
88  for (var i = 0; i < verticesOfShape.length; i += 3)
89  geom.vertices.push( new THREE.Vector3( verticesOfShape[i], verticesOfShape[i+1], verticesOfShape[i+2] ) );
90 
91  var color = new THREE.Color();
92 
93  for (var i = 0; i < indicesOfFaces.length; i += 3)
94  geom.faces.push( new THREE.Face3( indicesOfFaces[i], indicesOfFaces[i+1], indicesOfFaces[i+2], null, color, 0 ) );
95 
96  geom.computeFaceNormals();
97 
98  return geom;
99  }
100 
101 
102  JSROOT.GEO.createTrapezoid = function( shape ) {
103 
104  var y1, y2;
105  if (shape._typename == "TGeoTrd1") {
106  y1 = y2 = shape.fDY;
107  } else {
108  y1 = shape.fDy1; y2 = shape.fDy2;
109  }
110 
111  var verticesOfShape = [
112  -shape.fDx1, y1, -shape.fDZ,
113  shape.fDx1, y1, -shape.fDZ,
114  shape.fDx1, -y1, -shape.fDZ,
115  -shape.fDx1, -y1, -shape.fDZ,
116  -shape.fDx2, y2, shape.fDZ,
117  shape.fDx2, y2, shape.fDZ,
118  shape.fDx2, -y2, shape.fDZ,
119  -shape.fDx2, -y2, shape.fDZ
120  ];
121 
122  var indicesOfFaces = [
123  4,6,5, 4,7,6, 0,3,7, 7,4,0,
124  4,5,1, 1,0,4, 6,2,1, 1,5,6,
125  7,3,2, 2,6,7, 1,2,3, 3,0,1 ];
126 
127  var geometry = new THREE.Geometry();
128  for (var i = 0; i < 24; i += 3)
129  geometry.vertices.push( new THREE.Vector3( verticesOfShape[i], verticesOfShape[i+1], verticesOfShape[i+2] ) );
130 
131  var color = new THREE.Color();
132 
133  for (var i = 0; i < 36; i += 3)
134  geometry.faces.push( new THREE.Face3( indicesOfFaces[i], indicesOfFaces[i+1], indicesOfFaces[i+2], null, color, 0 ) );
135 
136  geometry.computeFaceNormals();
137  return geometry;
138  }
139 
140 
141  JSROOT.GEO.createArb8 = function( shape ) {
142 
143  var verticesOfShape = [
144  shape.fXY[0][0], shape.fXY[0][1], -shape.fDZ,
145  shape.fXY[1][0], shape.fXY[1][1], -shape.fDZ,
146  shape.fXY[2][0], shape.fXY[2][1], -shape.fDZ,
147  shape.fXY[3][0], shape.fXY[3][1], -shape.fDZ,
148  shape.fXY[4][0], shape.fXY[4][1], shape.fDZ,
149  shape.fXY[5][0], shape.fXY[5][1], shape.fDZ,
150  shape.fXY[6][0], shape.fXY[6][1], shape.fDZ,
151  shape.fXY[7][0], shape.fXY[7][1], shape.fDZ
152  ];
153 
154  var indicies = [];
155 
156  var indicesOfFaces = [
157  4,6,5, 4,7,6, 0,3,7, 7,4,0,
158  4,5,1, 1,0,4, 6,2,1, 1,5,6,
159  7,3,2, 2,6,7, 1,2,3, 3,0,1 ];
160 
161  var geometry = new THREE.Geometry();
162  for (var i = 0; i < 8; ++i) {
163  var ii = i*3;
164  if ((i>0) && (verticesOfShape[ii] === verticesOfShape[ii-3]) &&
165  (verticesOfShape[ii+1] === verticesOfShape[ii-2]) &&
166  (verticesOfShape[ii+2] === verticesOfShape[ii-1])) {
167  indicies[i] = indicies[i-1];
168  continue;
169  }
170 
171  indicies[i] = geometry.vertices.length;
172 
173  geometry.vertices.push( new THREE.Vector3( verticesOfShape[ii], verticesOfShape[ii+1], verticesOfShape[ii+2] ) );
174  }
175 
176  var color = new THREE.Color();
177 
178  for (var i = 0; i < 36; i += 3) {
179  var a = indicies[indicesOfFaces[i]],
180  b = indicies[indicesOfFaces[i+1]],
181  c = indicies[indicesOfFaces[i+2]];
182  if ((a!==b) && (b!==c) && (a!==c))
183  geometry.faces.push( new THREE.Face3( a, b, c, null, color, 0 ) );
184  }
185 
186  geometry.computeFaceNormals();
187  return geometry;
188  }
189 
190 
191  JSROOT.GEO.createSphere = function( shape, faces_limit ) {
192  var outerRadius = shape.fRmax;
193  var innerRadius = shape.fRmin;
194  var phiStart = shape.fPhi1 + 180;
195  var phiLength = shape.fPhi2 - shape.fPhi1;
196  var thetaStart = shape.fTheta1;
197  var thetaLength = shape.fTheta2 - shape.fTheta1;
198  var widthSegments = shape.fNseg;
199  var heightSegments = shape.fNz;
200 
201  var noInside = (innerRadius <= 0);
202 
203  if (faces_limit !== undefined) {
204  var fact = (noInside ? 2 : 4) * widthSegments * heightSegments / faces_limit;
205  if (fact > 1.) {
206  widthSegments = Math.round(widthSegments/Math.sqrt(fact));
207  heightSegments = Math.round(heightSegments/Math.sqrt(fact));
208  }
209  }
210 
211  var sphere = new THREE.SphereGeometry( outerRadius, widthSegments, heightSegments,
212  phiStart*Math.PI/180, phiLength*Math.PI/180, thetaStart*Math.PI/180, thetaLength*Math.PI/180);
213  sphere.applyMatrix( new THREE.Matrix4().makeRotationX( Math.PI / 2 ) );
214 
215  var geometry = new THREE.Geometry();
216  var color = new THREE.Color();
217 
218  // add outer sphere
219  for (var n=0; n < sphere.vertices.length; ++n)
220  geometry.vertices.push(sphere.vertices[n]);
221 
222  // add faces
223  for (var n=0; n < sphere.faces.length; ++n) {
224  var face = sphere.faces[n];
225  geometry.faces.push(new THREE.Face3( face.a, face.b, face.c, null, color, 0 ) );
226  }
227 
228  var shift = geometry.vertices.length;
229 
230  if (noInside) {
231  // simple sphere without inner cut
232  if ((thetaLength === 180) && (phiLength === 360)) {
233  geometry.computeFaceNormals();
234  return geometry;
235  }
236 
237  geometry.vertices.push(new THREE.Vector3(0, 0, 0));
238  } else {
239  var k = innerRadius / outerRadius;
240 
241  // add inner sphere
242  for (var n=0; n < sphere.vertices.length; ++n) {
243  var v = sphere.vertices[n];
244  geometry.vertices.push(new THREE.Vector3(k*v.x, k*v.y, k*v.z));
245  }
246  for (var n=0; n < sphere.faces.length; ++n) {
247  var face = sphere.faces[n];
248  geometry.faces.push(new THREE.Face3( shift+face.b, shift+face.a, shift+face.c, null, color, 0 ) );
249  }
250  }
251 
252  if (thetaLength !== 180) {
253  // add top cap
254  for (var i = 0; i < widthSegments; ++i) {
255  if (noInside) {
256  geometry.faces.push( new THREE.Face3( i+0, i+1, shift, null, color, 0 ) );
257  } else {
258  geometry.faces.push( new THREE.Face3( i+0, i+1, i+shift, null, color, 0 ) );
259  geometry.faces.push( new THREE.Face3( i+1, i+shift+1, i+shift, null, color, 0 ) );
260  }
261  }
262 
263  var dshift = sphere.vertices.length - widthSegments - 1;
264 
265  // add bottom cap
266  for (var i = dshift; i < dshift + widthSegments; ++i) {
267  if (noInside) {
268  geometry.faces.push( new THREE.Face3( i+0, i+1, shift, null, color, 0 ) );
269  } else {
270  geometry.faces.push( new THREE.Face3( i+1, i+0, i+shift, null, color, 0 ) );
271  geometry.faces.push( new THREE.Face3( i+shift+1, i+1, i+shift, null, color, 0 ) );
272  }
273  }
274  }
275 
276  if (phiLength !== 360) {
277  // one cuted side
278  for (var j=0; j<heightSegments; j++) {
279  var i1 = j*(widthSegments+1);
280  var i2 = (j+1)*(widthSegments+1);
281  if (noInside) {
282  geometry.faces.push( new THREE.Face3( i1, i2, shift, null, color, 0 ) );
283  } else {
284  geometry.faces.push( new THREE.Face3( i2, i1, i1+shift, null, color, 0 ) );
285  geometry.faces.push( new THREE.Face3( i2+shift, i2, i1+shift, null, color, 0 ));
286  }
287  }
288  // another cuted side
289  for (var j=0;j<heightSegments;j++) {
290  var i1 = (j+1)*(widthSegments+1) - 1;
291  var i2 = (j+2)*(widthSegments+1) - 1;
292  if (noInside) {
293  geometry.faces.push( new THREE.Face3( i1, i2, shift, null, color, 0 ) );
294  } else {
295  geometry.faces.push( new THREE.Face3( i1, i2, i1+shift, null, color, 0 ) );
296  geometry.faces.push( new THREE.Face3( i2, i2+shift, i1+shift, null, color, 0));
297  }
298  }
299  }
300 
301  geometry.computeFaceNormals();
302 
303  return geometry;
304  }
305 
306 
307  JSROOT.GEO.createTube = function( shape ) {
308  var outerRadius1, innerRadius1, outerRadius2, innerRadius2;
309  if ((shape._typename == "TGeoCone") || (shape._typename == "TGeoConeSeg")) {
310  outerRadius1 = shape.fRmax2;
311  innerRadius1 = shape.fRmin2;
312  outerRadius2 = shape.fRmax1;
313  innerRadius2 = shape.fRmin1;
314  } else {
315  outerRadius1 = outerRadius2 = shape.fRmax;
316  innerRadius1 = innerRadius2 = shape.fRmin;
317  }
318 
319  var hasrmin = (innerRadius1 > 0) || (innerRadius2 > 0);
320 
321  if (hasrmin) {
322  if (innerRadius1 <= 0) { innerRadius1 = 0.0000001; console.warn('zero inner radius1 in tube - not yet supported'); }
323  if (innerRadius2 <= 0) { innerRadius2 = 0.0000001; console.warn('zero inner radius1 in tube - not yet supported'); }
324  }
325 
326  var thetaStart = 0, thetaLength = 360;
327  if ((shape._typename == "TGeoConeSeg") || (shape._typename == "TGeoTubeSeg") || (shape._typename == "TGeoCtub")) {
328  thetaStart = shape.fPhi1;
329  thetaLength = shape.fPhi2 - shape.fPhi1;
330  }
331 
332  var radiusSegments = Math.floor(thetaLength/6);
333  if (radiusSegments < 4) radiusSegments = 4;
334 
335  var extrapnt = (thetaLength < 360) ? 1 : 0;
336 
337  var nsegm = radiusSegments + extrapnt;
338 
339  var phi0 = thetaStart*Math.PI/180, dphi = thetaLength/radiusSegments*Math.PI/180;
340 
341  // calculate all sin/cos tables in advance
342  var _sin = new Float32Array(nsegm), _cos = new Float32Array(nsegm);
343  for (var seg=0; seg<nsegm; ++seg) {
344  _cos[seg] = Math.cos(phi0+seg*dphi);
345  _sin[seg] = Math.sin(phi0+seg*dphi);
346  }
347 
348  var geometry = new THREE.Geometry();
349 
350  // add inner tube vertices
351 
352  if (hasrmin) {
353  for (var seg=0; seg<nsegm; ++seg)
354  geometry.vertices.push( new THREE.Vector3( innerRadius1*_cos[seg], innerRadius1*_sin[seg], shape.fDZ));
355  for (var seg=0; seg<nsegm; ++seg)
356  geometry.vertices.push( new THREE.Vector3( innerRadius2*_cos[seg], innerRadius2*_sin[seg], -shape.fDZ));
357  } else {
358  geometry.vertices.push( new THREE.Vector3( 0, 0, shape.fDZ));
359  geometry.vertices.push( new THREE.Vector3( 0, 0, -shape.fDZ));
360  }
361 
362  var shift = geometry.vertices.length;
363 
364  // add outer tube vertices
365  for (var seg=0; seg<nsegm; ++seg)
366  geometry.vertices.push( new THREE.Vector3( outerRadius1*_cos[seg], outerRadius1*_sin[seg], shape.fDZ));
367  for (var seg=0; seg<nsegm; ++seg)
368  geometry.vertices.push( new THREE.Vector3( outerRadius2*_cos[seg], outerRadius2*_sin[seg], -shape.fDZ));
369 
370  // recalculate Z of all vertices for ctub shape
371  if (shape._typename == "TGeoCtub")
372  for (var n=0;n<geometry.vertices.length;++n) {
373  var vertex = geometry.vertices[n];
374  if (vertex.z<0) vertex.z = -shape.fDz-(vertex.x*shape.fNlow[0]+vertex.x*shape.fNlow[1])/shape.fNlow[2];
375  else vertex.z = shape.fDz-(vertex.y*shape.fNhigh[0]+vertex.y*shape.fNhigh[1])/shape.fNhigh[2];
376  }
377 
378  var color = new THREE.Color(); // make dummy color for all faces
379 
380  // add inner tube faces
381  if (hasrmin)
382  for (var seg=0; seg<radiusSegments; ++seg) {
383  var seg1 = (extrapnt === 1) ? (seg + 1) : (seg + 1) % radiusSegments;
384  geometry.faces.push( new THREE.Face3( nsegm + seg, seg, seg1, null, color, 0 ) );
385  geometry.faces.push( new THREE.Face3( nsegm + seg, seg1, nsegm + seg1, null, color, 0 ) );
386  }
387 
388  // add outer tube faces
389  for (var seg=0; seg<radiusSegments; ++seg) {
390  var seg1 = (extrapnt === 1) ? (seg + 1) : (seg + 1) % radiusSegments;
391  geometry.faces.push( new THREE.Face3( shift+seg, shift + nsegm + seg, shift + seg1, null, color, 0 ) );
392  geometry.faces.push( new THREE.Face3( shift + nsegm + seg, shift + nsegm + seg1, shift + seg1, null, color, 0 ) );
393  }
394 
395 
396  // add top cap
397  for (var i = 0; i < radiusSegments; ++i){
398  var i1 = (extrapnt === 1) ? (i+1) : (i+1) % radiusSegments;
399  if (hasrmin) {
400  geometry.faces.push( new THREE.Face3( i, i+shift, i1, null, color, 0 ) );
401  geometry.faces.push( new THREE.Face3( i+shift, i1+shift, i1, null, color, 0 ) );
402  } else {
403  geometry.faces.push( new THREE.Face3( 0, i+shift, i1+shift, null, color, 0 ) );
404  }
405  }
406 
407  // add bottom cap
408  for (var i = 0; i < radiusSegments; ++i) {
409  var i1 = (extrapnt === 1) ? (i+1) : (i+1) % radiusSegments;
410  if (hasrmin) {
411  geometry.faces.push( new THREE.Face3( nsegm+i+shift, nsegm+i, nsegm+i1, null, color, 0 ) );
412  geometry.faces.push( new THREE.Face3( nsegm+i+shift, nsegm+i1, nsegm+i1+shift, null, color, 0 ) );
413  } else {
414  geometry.faces.push( new THREE.Face3( nsegm+i+shift, 1, nsegm+i1+shift, null, color, 0 ) );
415  }
416  }
417 
418  // close cut regions
419  if (extrapnt === 1) {
420  if (hasrmin) {
421  geometry.faces.push( new THREE.Face3( 0, nsegm, shift+nsegm, null, color, 0 ) );
422  geometry.faces.push( new THREE.Face3( 0, shift+nsegm, shift, null, color, 0 ) );
423  } else {
424  geometry.faces.push( new THREE.Face3( 0, 1, shift+nsegm, null, color, 0 ) );
425  geometry.faces.push( new THREE.Face3( 0, shift+nsegm, shift, null, color, 0 ) );
426  }
427 
428  if (hasrmin) {
429  geometry.faces.push( new THREE.Face3( radiusSegments, shift+2*radiusSegments+1, 2*radiusSegments+1, null, color, 0 ) );
430  geometry.faces.push( new THREE.Face3( radiusSegments, shift + radiusSegments, shift+2*radiusSegments+1, null, color, 0 ) );
431  } else {
432  geometry.faces.push( new THREE.Face3( 0, shift+2*radiusSegments+1, 1, null, color, 0 ) );
433  geometry.faces.push( new THREE.Face3( 0, shift + radiusSegments, shift+2*radiusSegments+1, null, color, 0 ) );
434  }
435  }
436 
437  geometry.computeFaceNormals();
438 
439  return geometry;
440  }
441 
442 
443  JSROOT.GEO.createEltu = function( shape ) {
444  var geometry = new THREE.Geometry();
445 
446  var radiusSegments = Math.floor(360/6);
447 
448  // calculate all sin/cos tables in advance
449  var x = new Float32Array(radiusSegments),
450  y = new Float32Array(radiusSegments);
451  for (var seg=0; seg<radiusSegments; ++seg) {
452  var phi = seg/radiusSegments*2*Math.PI;
453  x[seg] = shape.fRmin*Math.cos(phi);
454  y[seg] = shape.fRmax*Math.sin(phi);
455  }
456 
457  // create vertices
458  for (var seg=0; seg<radiusSegments; ++seg)
459  geometry.vertices.push( new THREE.Vector3( x[seg], y[seg], -shape.fDZ));
460  geometry.vertices.push( new THREE.Vector3( 0, 0, -shape.fDZ));
461 
462  for (var seg=0; seg<radiusSegments; ++seg)
463  geometry.vertices.push( new THREE.Vector3( x[seg], y[seg], +shape.fDZ));
464  geometry.vertices.push( new THREE.Vector3( 0, 0, shape.fDZ));
465 
466  var color = new THREE.Color();
467 
468  // create tube faces
469  for (var seg=0; seg<radiusSegments; ++seg) {
470  var seg1 = (seg + 1) % radiusSegments;
471  geometry.faces.push( new THREE.Face3( seg+radiusSegments+1, seg, seg1, null, color, 0 ) );
472  geometry.faces.push( new THREE.Face3( seg+radiusSegments+1, seg1, seg1+radiusSegments+1, null, color, 0 ) );
473  }
474 
475  // create bottom cap
476  for (var seg=0; seg<radiusSegments; ++seg)
477  geometry.faces.push( new THREE.Face3( seg, radiusSegments, (seg + 1) % radiusSegments, null, color, 0 ));
478 
479  // create upper cap
480  var shift = radiusSegments + 1;
481  for (var seg=0; seg<radiusSegments; ++seg)
482  geometry.faces.push( new THREE.Face3( shift+seg, shift+ (seg + 1) % radiusSegments, shift+radiusSegments, null, color, 0 ));
483 
484  geometry.computeFaceNormals();
485  return geometry;
486  }
487 
488 
489  JSROOT.GEO.createTorus = function( shape, faces_limit ) {
490  var radius = shape.fR;
491  var innerTube = shape.fRmin;
492  var outerTube = shape.fRmax;
493  var arc = shape.fDphi - shape.fPhi1;
494  var rotation = shape.fPhi1;
495  var radialSegments = 30;
496  var tubularSegments = Math.floor(arc/6);
497  if (tubularSegments < 8) tubularSegments = 8;
498 
499  var hasrmin = innerTube > 0, hascut = arc !== 360;
500 
501  if (faces_limit !== undefined) {
502  var fact = (hasrmin ? 4 : 2) * (radialSegments + 1) * tubularSegments / faces_limit;
503  if (fact > 1.) {
504  radialSegments = Math.round(radialSegments/Math.sqrt(fact));
505  tubularSegments = Math.round(tubularSegments/Math.sqrt(fact));
506  }
507  }
508 
509  var geometry = new THREE.Geometry();
510  var color = new THREE.Color();
511 
512  var outerTorus = new THREE.TorusGeometry( radius, outerTube, radialSegments, tubularSegments, arc*Math.PI/180);
513  outerTorus.applyMatrix( new THREE.Matrix4().makeRotationZ(rotation*Math.PI/180) );
514 
515  // add outer torus
516  for (var n=0; n < outerTorus.vertices.length; ++n)
517  geometry.vertices.push(outerTorus.vertices[n]);
518 
519  for (var n=0; n < outerTorus.faces.length; ++n) {
520  var face = outerTorus.faces[n];
521  geometry.faces.push(new THREE.Face3( face.a, face.b, face.c, null, color, 0 ) );
522  }
523 
524  var shift = geometry.vertices.length;
525 
526  if (hasrmin) {
527  var innerTorus = new THREE.TorusGeometry( radius, innerTube, radialSegments, tubularSegments, arc*Math.PI/180);
528  innerTorus.applyMatrix( new THREE.Matrix4().makeRotationZ(rotation*Math.PI/180) );
529 
530  // add inner torus
531  for (var n=0; n < innerTorus.vertices.length; ++n)
532  geometry.vertices.push(innerTorus.vertices[n]);
533 
534  for (var n=0; n < innerTorus.faces.length; ++n) {
535  var face = innerTorus.faces[n];
536  geometry.faces.push(new THREE.Face3( shift+face.a, shift+face.c, shift+face.b, null, color, 0 ) );
537  }
538  } else
539  if (hascut) {
540  geometry.vertices.push(new THREE.Vector3(radius*Math.cos(rotation*Math.PI/180), radius*Math.sin(rotation*Math.PI/180),0));
541  geometry.vertices.push(new THREE.Vector3(radius*Math.cos((rotation+arc)*Math.PI/180), radius*Math.sin((rotation+arc)*Math.PI/180),0));
542  }
543 
544  if (arc !== 360) {
545  // one cuted side
546  for (var j=0;j<radialSegments;j++) {
547  var i1 = j*(tubularSegments+1);
548  var i2 = (j+1)*(tubularSegments+1);
549  if (hasrmin) {
550  geometry.faces.push( new THREE.Face3( i2, i1+shift, i1, null, color, 0 ) );
551  geometry.faces.push( new THREE.Face3( i2, i2+shift, i1+shift, null, color, 0 ));
552  } else {
553  geometry.faces.push( new THREE.Face3( shift, i1, i2, null, color, 0 ));
554  }
555  }
556 
557  // another cuted side
558  for (var j=0;j<radialSegments;j++) {
559  var i1 = (j+1)*(tubularSegments+1)-1;
560  var i2 = (j+2)*(tubularSegments+1)-1;
561  if (hasrmin) {
562  geometry.faces.push( new THREE.Face3( i2, i1, i1+shift, null, color, 0 ) );
563  geometry.faces.push( new THREE.Face3( i2, i1+shift, i2+shift, null, color, 0 ));
564  } else {
565  geometry.faces.push( new THREE.Face3( shift+1, i2, i1, null, color, 0 ));
566  }
567  }
568  }
569 
570  geometry.computeFaceNormals();
571 
572  return geometry;
573  }
574 
575 
576  JSROOT.GEO.createPolygon = function( shape ) {
577 
578  var thetaStart = shape.fPhi1, thetaLength = shape.fDphi;
579 
580  var radiusSegments = 60;
581  if ( shape._typename == "TGeoPgon" ) {
582  radiusSegments = shape.fNedges;
583  } else {
584  radiusSegments = Math.floor(thetaLength/6);
585  if (radiusSegments < 4) radiusSegments = 4;
586  }
587 
588  var geometry = new THREE.Geometry();
589 
590  var color = new THREE.Color();
591 
592  var phi0 = thetaStart*Math.PI/180, dphi = thetaLength/radiusSegments*Math.PI/180;
593 
594  // calculate all sin/cos tables in advance
595  var _sin = new Float32Array(radiusSegments+1), _cos = new Float32Array(radiusSegments+1);
596  for (var seg=0;seg<=radiusSegments;++seg) {
597  _cos[seg] = Math.cos(phi0+seg*dphi);
598  _sin[seg] = Math.sin(phi0+seg*dphi);
599  }
600 
601  var indxs = [[],[]], pnts = null, edges = null; // remember indexes for each layer
602  var layerVerticies = radiusSegments; // how many verticies in one layer
603 
604  if (thetaLength !== 360) {
605  pnts = []; // coordinate of point on cut edge (x,z)
606  edges = []; // number of layer for that points
607  layerVerticies+=1; // one need one more vertice
608  }
609 
610  var a,b,c,d,e; // used for face swapping
611 
612  for (var side = 0; side < 2; ++side) {
613 
614  var rside = (side === 0) ? 'fRmax' : 'fRmin';
615  var prev_indx = geometry.vertices.length;
616 
617  for (var layer=0; layer < shape.fNz; ++layer) {
618 
619  indxs[side][layer] = geometry.vertices.length;
620 
621  // first create points for the layer
622  var layerz = shape.fZ[layer], rad = shape[rside][layer];
623 
624  if ((layer > 0) && (layer < shape.fNz-1)) {
625  if (((shape.fZ[layer-1] === layerz) && (shape[rside][layer-1] === rad)) ||
626  ((shape[rside][layer+1] === rad) && (shape[rside][layer-1] === rad))) {
627 
628  // same Z and R as before - ignore
629  // or same R before and after
630  indxs[side][layer] = indxs[side][layer-1];
631  // if (len) len[side][layer] = len[side][layer-1];
632  continue;
633  }
634  }
635 
636  if (rad <= 0.) rad = 0.000001;
637 
638  var curr_indx = geometry.vertices.length;
639 
640  // create vertices for the layer
641  for (var seg=0; seg < layerVerticies; ++seg)
642  geometry.vertices.push( new THREE.Vector3( rad*_cos[seg], rad*_sin[seg], layerz ));
643 
644  if (pnts !== null) {
645  if (side === 0) {
646  pnts.push(new THREE.Vector2(rad, layerz));
647  edges.push(curr_indx);
648  } else
649  if (rad < shape.fRmax[layer]) {
650  pnts.unshift(new THREE.Vector2(rad, layerz));
651  edges.unshift(curr_indx);
652  }
653  }
654 
655  if (layer>0) // create faces
656  for (var seg=0;seg < radiusSegments;++seg) {
657  var seg1 = (seg + 1) % layerVerticies;
658  geometry.faces.push( new THREE.Face3( prev_indx + seg, (side === 0) ? (prev_indx + seg1) : (curr_indx + seg) , curr_indx + seg1, null, color, 0 ) );
659  geometry.faces.push( new THREE.Face3( prev_indx + seg, curr_indx + seg1, (side === 0) ? (curr_indx + seg) : prev_indx + seg1, null, color, 0 ));
660  }
661 
662  prev_indx = curr_indx;
663  }
664  }
665 
666  // add faces for top and bottom side
667  for (var layer = 0; layer < shape.fNz; layer+= (shape.fNz-1)) {
668  if (shape.fRmin[layer] >= shape.fRmax[layer]) continue;
669  var inside = indxs[1][layer], outside = indxs[0][layer];
670  for (var seg=0; seg < radiusSegments; ++seg) {
671  var seg1 = (seg + 1) % layerVerticies;
672  geometry.faces.push( new THREE.Face3( outside + seg, (layer===0) ? (inside + seg) : (outside + seg1), inside + seg1, null, color, 0 ) );
673  geometry.faces.push( new THREE.Face3( outside + seg, inside + seg1, (layer===0) ? (outside + seg1) : (inside + seg), null, color, 0 ));
674  }
675  }
676 
677  if (pnts!==null) {
678  var faces = [];
679  if (pnts.length === shape.fNz * 2) {
680  // special case - all layers are there, create faces ourself
681  for (var layer = shape.fNz-1; layer>0; --layer) {
682  if (shape.fZ[layer] === shape.fZ[layer-1]) continue;
683  var right = 2*shape.fNz - 1 - layer;
684  faces.push([right, layer - 1, layer]);
685  faces.push([right, right + 1, layer-1]);
686  }
687 
688  } else {
689  // let three.js calculate our faces
690  faces = THREE.ShapeUtils.triangulateShape(pnts, []);
691  }
692 
693  for (var i = 0; i < faces.length; ++i) {
694  var f = faces[i];
695  geometry.faces.push( new THREE.Face3( edges[f[0]], edges[f[1]], edges[f[2]], null, color, 0) );
696  }
697  for (var i = 0; i < faces.length; ++i) {
698  var f = faces[i];
699  geometry.faces.push( new THREE.Face3( edges[f[0]] + radiusSegments, edges[f[2]] + radiusSegments, edges[f[1]] + radiusSegments, null, color, 0) );
700  }
701  }
702 
703  geometry.computeFaceNormals();
704 
705  return geometry;
706  }
707 
708 
709  JSROOT.GEO.createXtru = function( shape ) {
710 
711  var geometry = new THREE.Geometry();
712 
713  var fcolor = new THREE.Color();
714 
715  var prev = 0, curr = 0;
716  for (var layer = 0; layer < shape.fNz; ++layer) {
717  var layerz = shape.fZ[layer], scale = shape.fScale[layer];
718 
719  prev = curr;
720  curr = geometry.vertices.length;
721 
722  // add vertices
723  for (var vert = 0; vert < shape.fNvert; ++vert)
724  geometry.vertices.push( new THREE.Vector3( scale * shape.fX[vert], scale * shape.fY[vert], layerz ));
725 
726  if (layer>0) // create faces for sides
727  for (var vert = 0; vert < shape.fNvert; ++vert) {
728  var vert1 = (vert + 1) % shape.fNvert;
729  geometry.faces.push( new THREE.Face3( prev + vert, curr + vert, curr + vert1, null, fcolor, 0 ) );
730  geometry.faces.push( new THREE.Face3( prev + vert, curr + vert1, prev + vert1, null, fcolor, 0 ));
731  }
732  }
733 
734  // now try to make shape - use standard THREE.js utils
735 
736  var pnts = [];
737  for (var vert = 0; vert < shape.fNvert; ++vert)
738  pnts.push( new THREE.Vector2(shape.fX[vert], shape.fY[vert]));
739  var faces = THREE.ShapeUtils.triangulateShape(pnts, []);
740 
741  for (var i = 0; i < faces.length; ++i) {
742  face = faces[ i ];
743  geometry.faces.push( new THREE.Face3( face[1], face[0], face[2], null, fcolor, 0) );
744  geometry.faces.push( new THREE.Face3( face[0] + curr, face[1] + curr, face[2] + curr, null, fcolor, 0) );
745  }
746 
747  geometry.computeFaceNormals();
748 
749  return geometry;
750  }
751 
752 
753  JSROOT.GEO.createParaboloid = function( shape, faces_limit ) {
754 
755  var radiusSegments = Math.round(360/6), heightSegments = 30;
756 
757  if (faces_limit !== undefined) {
758  var fact = 2 * (radiusSegments+1) * (heightSegments+1) / faces_limit;
759  if (fact > 1.) {
760  radiusSegments = Math.round(radiusSegments/Math.sqrt(fact));
761  heightSegments = Math.round(heightSegments/Math.sqrt(fact));
762  }
763  }
764 
765  // calculate all sin/cos tables in advance
766  var _sin = new Float32Array(radiusSegments), _cos = new Float32Array(radiusSegments);
767  for (var seg=0;seg<radiusSegments;++seg) {
768  _cos[seg] = Math.cos(seg/radiusSegments*2*Math.PI);
769  _sin[seg] = Math.sin(seg/radiusSegments*2*Math.PI);
770  }
771 
772  var geometry = new THREE.Geometry();
773  var fcolor = new THREE.Color();
774 
775  var zmin = -shape.fDZ, zmax = shape.fDZ, rmin = shape.fRlo, rmax = shape.fRhi;
776 
777  // if no radius at -z, find intersection
778  if (shape.fA >= 0) {
779  if (shape.fB > zmin) zmin = shape.fB;
780  } else {
781  if (shape.fB < zmax) zmax = shape.fB;
782  }
783 
784  var ttmin = Math.atan2(zmin, rmin), ttmax = Math.atan2(zmax, rmax);
785 
786  var prev_indx = 0, prev_radius = 0;
787 
788  for (var layer = 0; layer <= heightSegments + 1; ++layer) {
789  var layerz = zmax, radius = 0;
790 
791  if ((layer === heightSegments + 1) && (prev_radius === 0)) break;
792 
793  switch (layer) {
794  case 0: layerz = zmin; radius = rmin; break;
795  case heightSegments: layerz = zmax; radius = rmax; break;
796  case heightSegments + 1: layerz = zmax; radius = 0; break;
797  default: {
798  var tt = Math.tan(ttmin + (ttmax-ttmin) * layer / heightSegments);
799  var delta = tt*tt - 4*shape.fA*shape.fB; // should be always positive (a*b<0)
800  radius = 0.5*(tt+Math.sqrt(delta))/shape.fA;
801  if (radius < 1e-6) radius = 0;
802  layerz = radius*tt;
803  }
804  }
805 
806  var curr_indx = geometry.vertices.length;
807 
808  if (radius === 0) {
809  geometry.vertices.push( new THREE.Vector3( 0, 0, layerz ));
810  } else {
811  for (var seg=0; seg<radiusSegments; ++seg)
812  geometry.vertices.push( new THREE.Vector3( radius*_cos[seg], radius*_sin[seg], layerz));
813  }
814 
815  // add faces of next layer
816  if (layer>0) {
817  for (var seg=0; seg<radiusSegments; ++seg) {
818  var seg1 = (seg+1) % radiusSegments;
819  if (prev_radius === 0) {
820  geometry.faces.push( new THREE.Face3( prev_indx, curr_indx + seg1, curr_indx + seg, null, fcolor, 0) );
821  } else
822  if (radius == 0) {
823  geometry.faces.push( new THREE.Face3( prev_indx + seg, prev_indx + seg1, curr_indx, null, fcolor, 0) );
824  } else {
825  geometry.faces.push( new THREE.Face3( prev_indx + seg, curr_indx + seg1, curr_indx + seg, null, fcolor, 0) );
826  geometry.faces.push( new THREE.Face3( prev_indx + seg, prev_indx + seg1, curr_indx + seg1, null, fcolor, 0) );
827  }
828  }
829  }
830 
831  prev_radius = radius;
832  prev_indx = curr_indx;
833  }
834 
835  geometry.computeFaceNormals();
836 
837  return geometry;
838  }
839 
840 
841  JSROOT.GEO.createHype = function( shape, faces_limit ) {
842 
843  if ((shape.fTin===0) && (shape.fTout===0))
844  return JSROOT.GEO.createTube(shape);
845 
846  var radiusSegments = Math.round(360/6), heightSegments = 30;
847 
848  if (faces_limit !== undefined) {
849  var fact = ((shape.fRmin <= 0) ? 2 : 4) * (radiusSegments+1) * (heightSegments+2) / faces_limit;
850  if (fact > 1.) {
851  radiusSegments = Math.round(radiusSegments/Math.sqrt(fact));
852  heightSegments = Math.round(heightSegments/Math.sqrt(fact));
853  }
854  }
855 
856  // calculate all sin/cos tables in advance
857  var _sin = new Float32Array(radiusSegments), _cos = new Float32Array(radiusSegments);
858  for (var seg=0;seg<radiusSegments;++seg) {
859  _cos[seg] = Math.cos(seg/radiusSegments*2*Math.PI);
860  _sin[seg] = Math.sin(seg/radiusSegments*2*Math.PI);
861  }
862 
863  var geometry = new THREE.Geometry();
864  var fcolor = new THREE.Color();
865 
866  var indexes = [[],[]];
867 
868  // in-out side
869  for (var side=0;side<2;++side) {
870 
871  // add only points, no faces
872  if ((side===0) && (shape.fRmin <= 0)) {
873  indexes[side][0] = geometry.vertices.length;
874  geometry.vertices.push( new THREE.Vector3( 0, 0, -shape.fDz ) );
875  indexes[side][heightSegments] = geometry.vertices.length;
876  geometry.vertices.push( new THREE.Vector3( 0, 0, shape.fDz ) );
877  continue;
878  }
879 
880  var prev_indx = 0;
881  var r0 = (side===0) ? shape.fRmin : shape.fRmax;
882  var tsq = (side===0) ? shape.fTinsq : shape.fToutsq;
883 
884  // vertical layers
885  for (var layer=0;layer<=heightSegments;++layer) {
886  var layerz = -shape.fDz + layer/heightSegments*2*shape.fDz;
887 
888  var radius = Math.sqrt(r0*r0+tsq*layerz*layerz);
889  var curr_indx = geometry.vertices.length;
890 
891  indexes[side][layer] = curr_indx;
892 
893  for (var seg=0; seg<radiusSegments; ++seg)
894  geometry.vertices.push( new THREE.Vector3( radius*_cos[seg], radius*_sin[seg], layerz));
895 
896  // add faces of next layer
897  if (layer>0) {
898  for (var seg=0; seg<radiusSegments; ++seg) {
899  var seg1 = (seg+1) % radiusSegments;
900  geometry.faces.push( new THREE.Face3( prev_indx + seg, (side===0) ? (curr_indx + seg) : (prev_indx + seg1), curr_indx + seg1, null, fcolor, 0) );
901  geometry.faces.push( new THREE.Face3( prev_indx + seg, curr_indx + seg1, (side===0) ? (prev_indx + seg1) : (curr_indx + seg), null, fcolor, 0) );
902  }
903  }
904 
905  prev_indx = curr_indx;
906  }
907  }
908 
909  // add caps
910  for(var layer=0; layer<=heightSegments; layer+=heightSegments) {
911  var inside = indexes[0][layer], outside = indexes[1][layer];
912  for (var seg=0; seg<radiusSegments; ++seg) {
913  var seg1 = (seg+1) % radiusSegments;
914  if (shape.fRmin <= 0) {
915  geometry.faces.push( new THREE.Face3( inside, outside + (layer===0 ? seg1 : seg), outside + (layer===0 ? seg : seg1), null, fcolor, 0) );
916  } else {
917  geometry.faces.push( new THREE.Face3( inside + seg, (layer===0) ? (inside + seg1) : (outside + seg), outside + seg1, null, fcolor, 0) );
918  geometry.faces.push( new THREE.Face3( inside + seg, outside + seg1, (layer===0) ? (outside + seg) : (inside + seg1), null, fcolor, 0) );
919  }
920  }
921  }
922 
923  geometry.computeFaceNormals();
924 
925  return geometry;
926  }
927 
928  JSROOT.GEO.createMatrix = function(matrix) {
929 
930  if (matrix === null) return null;
931 
932  var translation_matrix = null, rotation_matrix = null;
933 
934  if (matrix._typename == 'TGeoTranslation') {
935  translation_matrix = matrix.fTranslation;
936  }
937  else if (matrix._typename == 'TGeoRotation') {
938  rotation_matrix = matrix.fRotationMatrix;
939  }
940  else if (matrix._typename == 'TGeoCombiTrans') {
941  translation_matrix = matrix.fTranslation;
942  if (matrix.fRotation !== null)
943  rotation_matrix = matrix.fRotation.fRotationMatrix;
944  }
945  else if (matrix._typename !== 'TGeoIdentity') {
946  console.log('unsupported matrix ' + matrix._typename);
947  }
948 
949  if ((translation_matrix === null) && (rotation_matrix === null)) return null;
950 
951  var res = new THREE.Matrix4();
952 
953  if (rotation_matrix !== null)
954  res.set(rotation_matrix[0], rotation_matrix[1], rotation_matrix[2], 0,
955  rotation_matrix[3], rotation_matrix[4], rotation_matrix[5], 0,
956  rotation_matrix[6], rotation_matrix[7], rotation_matrix[8], 0,
957  0, 0, 0, 1);
958 
959  if (translation_matrix !== null)
960  res.setPosition(new THREE.Vector3(translation_matrix[0], translation_matrix[1], translation_matrix[2]));
961 
962  return res;
963  }
964 
965  JSROOT.GEO.createComposite = function ( shape, faces_limit ) {
966 
967  if (faces_limit === undefined) faces_limit = 10000;
968 
969  var geom1 = JSROOT.GEO.createGeometry(shape.fNode.fLeft, faces_limit / 2);
970  geom1.computeVertexNormals();
971  var matrix1 = JSROOT.GEO.createMatrix(shape.fNode.fLeftMat);
972  if (matrix1!==null) {
973  if (matrix1.determinant() < -0.9) console.warn('Axis reflection in composite shape - not supported');
974  geom1.applyMatrix(matrix1);
975  }
976 
977  var geom2 = JSROOT.GEO.createGeometry(shape.fNode.fRight, faces_limit / 2);
978  geom2.computeVertexNormals();
979  var matrix2 = JSROOT.GEO.createMatrix(shape.fNode.fRightMat);
980  if (matrix2 !== null) {
981  if (matrix2.determinant() < -0.9) console.warn('Axis reflection in composite shape - not supported');
982  geom2.applyMatrix(matrix2);
983  }
984 
985  var bsp1 = new ThreeBSP(geom1);
986  var bsp2 = new ThreeBSP(geom2);
987  var bsp = null;
988 
989  if (shape.fNode._typename === 'TGeoIntersection')
990  bsp = bsp1.intersect(bsp2); // "*"
991  else
992  if (shape.fNode._typename === 'TGeoUnion')
993  bsp = bsp1.union(bsp2); // "+"
994  else
995  if (shape.fNode._typename === 'TGeoSubtraction')
996  bsp = bsp1.subtract(bsp2); // "/"
997 
998  if (bsp === null) {
999  console.warn('unsupported bool operation ' + shape.fNode._typename + ', use first geom');
1000  return geom1;
1001  }
1002 
1003  var res = bsp.toGeometry();
1004 
1005  // console.log('Composite shape left_faces ' + geom1.faces.length + ' right_faces ' + geom2.faces.length + ' res_faces ' + res.faces.length);
1006 
1007  return res;
1008  }
1009 
1010 
1011  JSROOT.GEO.createGeometry = function( shape, limit ) {
1012 
1013  switch (shape._typename) {
1014  case "TGeoBBox": return JSROOT.GEO.createCube( shape );
1015  case "TGeoPara": return JSROOT.GEO.createPara( shape );
1016  case "TGeoTrd1":
1017  case "TGeoTrd2": return JSROOT.GEO.createTrapezoid( shape );
1018  case "TGeoArb8":
1019  case "TGeoTrap":
1020  case "TGeoGtra": return JSROOT.GEO.createArb8( shape );
1021  case "TGeoSphere": return JSROOT.GEO.createSphere( shape, limit );
1022  case "TGeoCone":
1023  case "TGeoConeSeg":
1024  case "TGeoTube":
1025  case "TGeoTubeSeg":
1026  case "TGeoCtub": return JSROOT.GEO.createTube( shape );
1027  case "TGeoEltu": return JSROOT.GEO.createEltu( shape );
1028  case "TGeoTorus": return JSROOT.GEO.createTorus( shape, limit );
1029  case "TGeoPcon":
1030  case "TGeoPgon": return JSROOT.GEO.createPolygon( shape );
1031  case "TGeoXtru": return JSROOT.GEO.createXtru( shape );
1032  case "TGeoParaboloid": return JSROOT.GEO.createParaboloid( shape, limit );
1033  case "TGeoHype": return JSROOT.GEO.createHype( shape, limit );
1034  case "TGeoCompositeShape": return JSROOT.GEO.createComposite( shape, limit );
1035  case "TGeoShapeAssembly": return new THREE.Geometry();
1036  }
1037 
1038  return null;
1039  }
1040 
1045  // ======= Geometry painter================================================
1046 
1047 
1048  JSROOT.EGeoVisibilityAtt = {
1049  kVisOverride : JSROOT.BIT(0), // volume's vis. attributes are overidden
1050  kVisNone : JSROOT.BIT(1), // the volume/node is invisible, as well as daughters
1051  kVisThis : JSROOT.BIT(2), // this volume/node is visible
1052  kVisDaughters : JSROOT.BIT(3), // all leaves are visible
1053  kVisOneLevel : JSROOT.BIT(4), // first level daughters are visible
1054  kVisStreamed : JSROOT.BIT(5), // true if attributes have been streamed
1055  kVisTouched : JSROOT.BIT(6), // true if attributes are changed after closing geom
1056  kVisOnScreen : JSROOT.BIT(7), // true if volume is visible on screen
1057  kVisContainers : JSROOT.BIT(12), // all containers visible
1058  kVisOnly : JSROOT.BIT(13), // just this visible
1059  kVisBranch : JSROOT.BIT(14), // only a given branch visible
1060  kVisRaytrace : JSROOT.BIT(15) // raytracing flag
1061  };
1062 
1063  JSROOT.TestGeoAttBit = function(volume, f) {
1064  if (!('fGeoAtt' in volume)) return false;
1065  return (volume.fGeoAtt & f) !== 0;
1066  }
1067 
1068  JSROOT.ToggleGeoAttBit = function(volume, f) {
1069  if (!('fGeoAtt' in volume)) return false;
1070 
1071  volume.fGeoAtt = volume.fGeoAtt ^ (f & 0xffffff);
1072  }
1073 
1074  JSROOT.TGeoPainter = function( geometry ) {
1075  if ((geometry !== null) && (geometry._typename.indexOf('TGeoVolume') === 0))
1076  geometry = { _typename:"TGeoNode", fVolume: geometry, fName:"TopLevel" };
1077 
1078  JSROOT.TObjectPainter.call(this, geometry);
1079 
1080  this.Cleanup(true);
1081  }
1082 
1083  JSROOT.TGeoPainter.prototype = Object.create( JSROOT.TObjectPainter.prototype );
1084 
1085  JSROOT.TGeoPainter.prototype.CreateToolbar = function(args) {
1086  if ( this._toolbar !== null ) return;
1087  var painter = this;
1088  var buttonList = [{
1089  name: 'toImage',
1090  title: 'Save as PNG',
1091  icon: JSROOT.ToolbarIcons.camera,
1092  click: function() {
1093  var dataUrl = painter._renderer.domElement.toDataURL("image/png");
1094  dataUrl.replace("image/png", "image/octet-stream");
1095  var link = document.createElement('a');
1096  if (typeof link.download === 'string') {
1097  document.body.appendChild(link); //Firefox requires the link to be in the body
1098  link.download = "geometry.png";
1099  link.href = dataUrl;
1100  link.click();
1101  document.body.removeChild(link); //remove the link when done
1102  }
1103  }
1104  }];
1105  this._toolbar = new JSROOT.Toolbar( this.select_main(), [buttonList] );
1106  }
1107 
1108  JSROOT.TGeoPainter.prototype.decodeOptions = function(opt) {
1109  var res = { _grid: false, _bound: false, _debug: false, _full: false, maxlvl: -1, _axis:false, scale: new THREE.Vector3(1,1,1) };
1110 
1111  var _opt = JSROOT.GetUrlOption('_grid');
1112  if (_opt !== null && _opt == "true") res._grid = true;
1113  var _opt = JSROOT.GetUrlOption('_debug');
1114  if (_opt !== null && _opt == "true") { res._debug = true; res._grid = true; }
1115  if (_opt !== null && _opt == "bound") { res._debug = true; res._grid = true; res._bound = true; }
1116  if (_opt !== null && _opt == "full") { res._debug = true; res._grid = true; res._full = true; res._bound = true; }
1117 
1118  opt = opt.toLowerCase();
1119 
1120  if (opt.indexOf("all")>=0) {
1121  res.maxlvl = 9999;
1122  opt = opt.replace("all", " ");
1123  }
1124  if (opt.indexOf("limit")>=0) {
1125  res.maxlvl = 1111;
1126  opt = opt.replace("limit", " ");
1127  }
1128  if (opt.indexOf("invx")>=0) {
1129  res.scale.x = -1;
1130  opt = opt.replace("invx", " ");
1131  }
1132  if (opt.indexOf("invy")>=0) {
1133  res.scale.y = -1;
1134  opt = opt.replace("invy", " ");
1135  }
1136  if (opt.indexOf("invz")>=0) {
1137  res.scale.z = -1;
1138  opt = opt.replace("invz", " ");
1139  }
1140 
1141  var p = opt.indexOf("maxlvl");
1142  if (p>=0) {
1143  res.maxlvl = parseInt(opt.substr(p+6, 1));
1144  opt = opt.replace("maxlvl" + res.maxlvl, " ");
1145  }
1146 
1147  if (opt.indexOf("d")>=0) res._debug = true;
1148  if (opt.indexOf("g")>=0) res._grid = true;
1149  if (opt.indexOf("b")>=0) res._bound = true;
1150  if (opt.indexOf("f")>=0) res._full = true;
1151  if (opt.indexOf("a")>=0) { res._axis = true; res._yup = false; }
1152  if (opt.indexOf("y")>=0) res._yup = true;
1153  if (opt.indexOf("z")>=0) res._yup = false;
1154 
1155  return res;
1156  }
1157 
1158 
1159  JSROOT.TGeoPainter.prototype.addControls = function() {
1160 
1161  if (this._controls !== null) return;
1162 
1163  var painter = this;
1164 
1165  this.select_main().property('flex_block_drag', true);
1166 
1167  this._controls = new THREE.OrbitControls(this._camera, this._renderer.domElement);
1168  this._controls.enableDamping = false;
1169  this._controls.dampingFactor = 0.25;
1170  this._controls.enableZoom = true;
1171  this._controls.target.copy(this._lookat);
1172  this._controls.update();
1173 
1174  this._controls.addEventListener( 'change', function() { painter.Render3D(0); } );
1175 
1176  if ( this.options._debug || this.options._grid ) {
1177  this._tcontrols = new THREE.TransformControls( this._camera, this._renderer.domElement );
1178  this._scene.add( this._tcontrols );
1179  this._tcontrols.attach( this._toplevel );
1180  //this._tcontrols.setSize( 1.1 );
1181 
1182  window.addEventListener( 'keydown', function ( event ) {
1183  switch ( event.keyCode ) {
1184  case 81: // Q
1185  painter._tcontrols.setSpace( painter._tcontrols.space === "local" ? "world" : "local" );
1186  break;
1187  case 17: // Ctrl
1188  painter._tcontrols.setTranslationSnap( Math.ceil( painter._overall_size ) / 50 );
1189  painter._tcontrols.setRotationSnap( THREE.Math.degToRad( 15 ) );
1190  break;
1191  case 84: // T (Translate)
1192  painter._tcontrols.setMode( "translate" );
1193  break;
1194  case 82: // R (Rotate)
1195  painter._tcontrols.setMode( "rotate" );
1196  break;
1197  case 83: // S (Scale)
1198  painter._tcontrols.setMode( "scale" );
1199  break;
1200  case 187:
1201  case 107: // +, =, num+
1202  painter._tcontrols.setSize( painter._tcontrols.size + 0.1 );
1203  break;
1204  case 189:
1205  case 109: // -, _, num-
1206  painter._tcontrols.setSize( Math.max( painter._tcontrols.size - 0.1, 0.1 ) );
1207  break;
1208  }
1209  });
1210  window.addEventListener( 'keyup', function ( event ) {
1211  switch ( event.keyCode ) {
1212  case 17: // Ctrl
1213  painter._tcontrols.setTranslationSnap( null );
1214  painter._tcontrols.setRotationSnap( null );
1215  break;
1216  }
1217  });
1218 
1219  this._tcontrols.addEventListener( 'change', function() { painter.Render3D(0); } );
1220  }
1221 
1222  var raycaster = new THREE.Raycaster(), INTERSECTED = null;
1223 
1224  function findIntersection(mouse) {
1225  // find intersections
1226 
1227  // if (JSROOT.gStyle.Tooltip<=0) return tooltip.hide();
1228 
1229  raycaster.setFromCamera( mouse, painter._camera );
1230  var intersects = raycaster.intersectObjects(painter._scene.children, true);
1231  if (intersects.length > 0) {
1232  var pick = null;
1233  for (var i = 0; i < intersects.length; ++i) {
1234  if ('emissive' in intersects[i].object.material) {
1235  pick = intersects[i].object;
1236  break;
1237  }
1238  }
1239  if (pick && INTERSECTED != pick) {
1240  INTERSECTED = pick;
1241 
1242  var name = INTERSECTED.name;
1243 
1244  var p = INTERSECTED.parent;
1245  while ((p!==undefined) && (p!==null)) {
1246  if ('name' in p) name = p.name+'/'+name;
1247  p = p.parent;
1248  }
1249 
1250  // console.log('intersect ' + name);
1251  }
1252  } else {
1253  // INTERSECTED = null;
1254  }
1255  };
1256 
1257  function mousemove(e) {
1258  var mouse_x = ('offsetX' in e) ? e.offsetX : e.layerX;
1259  var mouse_y = ('offsetY' in e) ? e.offsetY : e.layerY;
1260  var mouse = { x: (mouse_x / painter._renderer.domElement.width) * 2 - 1,
1261  y: -(mouse_y / painter._renderer.domElement.height) * 2 + 1 };
1262 
1263  findIntersection(mouse);
1264  e.preventDefault();
1265  }
1266 
1267  this._renderer.domElement.addEventListener('mousemove', mousemove);
1268  }
1269 
1270  JSROOT.TGeoPainter.prototype.accountClear = function() {
1271  this._num_geom = 0;
1272  this._num_vertices = 0;
1273  this._num_faces = 0;
1274  this._num_meshes = 0;
1275  }
1276 
1277  JSROOT.TGeoPainter.prototype.accountGeom = function(geom, shape_typename) {
1278  // used to calculate statistic over created geometry
1279  if (geom === null) {
1280  if (!('unsupported_shapes' in this)) this.unsupported_shapes = [];
1281  if ((shape_typename !== undefined) && (this.unsupported_shapes.indexOf(shape_typename) < 0)) {
1282  this.unsupported_shapes.push(shape_typename);
1283  console.warn('Not supported ' + shape_typename);
1284  }
1285  return;
1286  }
1287 
1288  this._num_geom++;
1289  if (('vertices' in geom) && ('faces' in geom)) {
1290  this._num_vertices += geom.vertices.length;
1291  this._num_faces += geom.faces.length;
1292  } else {
1293 
1294  var attr = geom.getAttribute('position');
1295  // this._num_vertices += attr.count() / 3;
1296  // this._num_faces += geom.index.count() / 3;
1297  }
1298  }
1299 
1300  JSROOT.TGeoPainter.prototype.accountMesh = function(mesh) {
1301  // used to calculate statistic over created meshes
1302  if (mesh !== null) this._num_meshes++;
1303  }
1304 
1305  JSROOT.TGeoPainter.prototype.checkFlipping = function(parent, matrix, shape, geom, mesh_has_childs) {
1306  // check if matrix of element should be flipped
1307 
1308  var m = new THREE.Matrix4();
1309  m.multiplyMatrices( parent.matrixWorld, matrix);
1310  if (m.determinant() > -0.9) return geom;
1311 
1312  // we could not transform matrix of mesh with childs, need workaround
1313  if (mesh_has_childs) return null;
1314 
1315  var cnt = 0, flip = new THREE.Vector3(1,1,1);
1316 
1317  if (m.elements[0]===-1 && m.elements[1]=== 0 && m.elements[2] === 0) { flip.x = -1; cnt++; }
1318  if (m.elements[4]=== 0 && m.elements[5]===-1 && m.elements[6] === 0) { flip.y = -1; cnt++; }
1319  if (m.elements[8]=== 0 && m.elements[9]=== 0 && m.elements[10]===-1) { flip.z = -1; cnt++; }
1320 
1321  if ((cnt===0) || (cnt ===2)) {
1322  flip.set(1,1,1); cnt = 0;
1323  if (m.elements[0] + m.elements[1] + m.elements[2] === -1) { flip.x = -1; cnt++; }
1324  if (m.elements[4] + m.elements[5] + m.elements[6] === -1) { flip.y = -1; cnt++; }
1325  if (m.elements[8] + m.elements[9] + m.elements[10] === -1) { flip.z = -1; cnt++; }
1326  if ((cnt === 0) || (cnt === 2)) {
1327  // console.log('not found proper axis, use Z ' + JSON.stringify(flip) + ' m = ' + JSON.stringify(m.elements));
1328  flip.z = -flip.z;
1329  }
1330  }
1331 
1332  matrix.scale(flip);
1333 
1334  var gname = "_geom";
1335  if (flip.x<0) gname += "X";
1336  if (flip.y<0) gname += "Y";
1337  if (flip.z<0) gname += "Z";
1338 
1339  // if geometry with such flipping already was created - use it again
1340  if (gname in shape) return shape[gname];
1341 
1342  geom = geom.clone();
1343 
1344  geom.scale(flip.x, flip.y, flip.z);
1345 
1346  var face, d;
1347  for (var n=0;n<geom.faces.length;++n) {
1348  face = geom.faces[n];
1349  d = face.b; face.b = face.c; face.c = d;
1350  }
1351 
1352  //geom.computeBoundingSphere();
1353  geom.computeFaceNormals();
1354 
1355  shape[gname] = geom;
1356 
1357  this.accountGeom(geom);
1358 
1359  return geom;
1360  }
1361 
1362  JSROOT.TGeoPainter.prototype.getNodeProperties = function(node, visible) {
1363  // function return matrix, shape and material
1364 
1365  var volume = node.fVolume;
1366 
1367  var prop = { shape: volume.fShape, matrix: null };
1368 
1369  if (('fMatrix' in node) && (node.fMatrix !== null))
1370  prop.matrix = JSROOT.GEO.createMatrix(node.fMatrix);
1371  else
1372  if ((node._typename == "TGeoNodeOffset") && (node.fFinder !== null)) {
1373  // if (node.fFinder._typename === 'TGeoPatternParaX') { }
1374  // if (node.fFinder._typename === 'TGeoPatternParaY') { }
1375  // if (node.fFinder._typename === 'TGeoPatternParaZ') { }
1376  // if (node.fFinder._typename === 'TGeoPatternTrapZ') { }
1377  // if (node.fFinder._typename === 'TGeoPatternCylR') { }
1378  // if (node.fFinder._typename === 'TGeoPatternSphR') { }
1379  // if (node.fFinder._typename === 'TGeoPatternSphTheta') { }
1380  // if (node.fFinder._typename === 'TGeoPatternSphPhi') { }
1381  // if (node.fFinder._typename === 'TGeoPatternHoneycomb') { }
1382  if ((node.fFinder._typename === 'TGeoPatternX') ||
1383  (node.fFinder._typename === 'TGeoPatternY') ||
1384  (node.fFinder._typename === 'TGeoPatternZ')) {
1385  var _shift = node.fFinder.fStart + (node.fIndex + 0.5) * node.fFinder.fStep;
1386 
1387  prop.matrix = new THREE.Matrix4();
1388 
1389  switch (node.fFinder._typename.charAt(11)) {
1390  case 'X': prop.matrix.setPosition(new THREE.Vector3(_shift, 0, 0)); break;
1391  case 'Y': prop.matrix.setPosition(new THREE.Vector3(0, _shift, 0)); break;
1392  case 'Z': prop.matrix.setPosition(new THREE.Vector3(0, 0, _shift)); break;
1393  }
1394  } else
1395  if (node.fFinder._typename === 'TGeoPatternCylPhi') {
1396  var phi = (Math.PI/180)*(node.fFinder.fStart+(node.fIndex+0.5)*node.fFinder.fStep);
1397  var _cos = Math.cos(phi), _sin = Math.sin(phi);
1398 
1399  prop.matrix = new THREE.Matrix4();
1400 
1401  prop.matrix.set(_cos, -_sin, 0, 0,
1402  _sin, _cos, 0, 0,
1403  0, 0, 1, 0,
1404  0, 0, 0, 1);
1405  } else {
1406  console.warn('Unsupported pattern type ' + node.fFinder._typename);
1407  }
1408  }
1409 
1410  prop.material = null;
1411 
1412  if (visible) {
1413  var _transparent = false, _opacity = 1.0;
1414  if ((volume.fFillColor > 1) && (volume.fLineColor == 1))
1415  prop.fillcolor = JSROOT.Painter.root_colors[volume.fFillColor];
1416  else
1417  if (volume.fLineColor >= 0)
1418  prop.fillcolor = JSROOT.Painter.root_colors[volume.fLineColor];
1419 
1420  if (('fMedium' in volume) && (volume.fMedium !== null) &&
1421  ('fMaterial' in volume.fMedium) && (volume.fMedium.fMaterial !== null)) {
1422  var fillstyle = volume.fMedium.fMaterial.fFillStyle;
1423  var transparency = (fillstyle < 3000 || fillstyle > 3100) ? 0 : fillstyle - 3000;
1424  if (transparency > 0) {
1425  _transparent = true;
1426  _opacity = (100.0 - transparency) / 100.0;
1427  }
1428  if (prop.fillcolor === undefined)
1429  prop.fillcolor = JSROOT.Painter.root_colors[volume.fMedium.fMaterial.fFillColor];
1430  }
1431  if (prop.fillcolor === undefined)
1432  prop.fillcolor = "lightgrey";
1433 
1434  prop.material = new THREE.MeshLambertMaterial( { transparent: _transparent,
1435  opacity: _opacity, wireframe: false, color: prop.fillcolor,
1436  side: THREE.FrontSide, vertexColors: THREE.NoColors /*THREE.VertexColors*/,
1437  overdraw: 0. } );
1438  }
1439 
1440  return prop;
1441  }
1442 
1443  JSROOT.TGeoPainter.prototype.getEveNodeProperties = function(node, visible) {
1444 
1445  var prop = { shape: node.fShape };
1446 
1447  prop.material = null;
1448 
1449  if (visible) {
1450  var _transparent = false, _opacity = 1.0;
1451  if ( node.fRGBA[3] < 1.0) {
1452  _transparent = true;
1453  _opacity = node.fRGBA[3];
1454  }
1455  prop.fillcolor = new THREE.Color( node.fRGBA[0], node.fRGBA[1], node.fRGBA[2] );
1456  prop.material = new THREE.MeshLambertMaterial( { transparent: _transparent,
1457  opacity: _opacity, wireframe: false, color: prop.fillcolor,
1458  side: THREE.FrontSide, vertexColors: THREE.NoColors /*THREE.VertexColors */,
1459  overdraw: 0. } );
1460  }
1461 
1462  prop.matrix = new THREE.Matrix4();
1463 
1464  if (node.fTrans!==null) {
1465  prop.matrix.set(node.fTrans[0], node.fTrans[4], node.fTrans[8], 0,
1466  node.fTrans[1], node.fTrans[5], node.fTrans[9], 0,
1467  node.fTrans[2], node.fTrans[6], node.fTrans[10], 0,
1468  0, 0, 0, 1);
1469  // second - set position with proper sign
1470  prop.matrix.setPosition({ x: node.fTrans[12], y: node.fTrans[13], z: node.fTrans[14] });
1471  }
1472  return prop;
1473  }
1474 
1475 
1476  JSROOT.TGeoPainter.prototype.drawNode = function() {
1477 
1478  if ((this._stack == null) || (this._stack.length == 0)) return false;
1479 
1480  var arg = this._stack[this._stack.length - 1];
1481 
1482  // cut all volumes below 0 level
1483  // if (arg.lvl===0) { this._stack.pop(); return true; }
1484 
1485  var kind = this.NodeKind(arg.node);
1486  if (kind < 0) return false;
1487  var chlds = null;
1488 
1489  if (kind === 0) {
1490  chlds = (arg.node.fVolume.fNodes !== null) ? arg.node.fVolume.fNodes.arr : null;
1491  } else {
1492  chlds = (arg.node.fElements !== null) ? arg.node.fElements.arr : null;
1493  }
1494 
1495  if ('nchild' in arg) {
1496  // add next child
1497  if ((chlds === null) || (chlds.length <= arg.nchild)) {
1498  this._stack.pop();
1499  } else {
1500  this._stack.push({ toplevel: (arg.mesh ? arg.mesh : arg.toplevel),
1501  node: chlds[arg.nchild++] });
1502  }
1503  return true;
1504  }
1505 
1506  var prop = null;
1507 
1508  if (kind === 0)
1509  prop = this.getNodeProperties(arg.node, arg.node._visible);
1510  else
1511  prop = this.getEveNodeProperties(arg.node, arg.node._visible);
1512 
1513  var geom = null;
1514 
1515  if (prop.matrix === null) prop.matrix = new THREE.Matrix4();
1516 
1517  if ((prop.shape === null) && arg.node._visible)
1518  arg.node._visible = false;
1519 
1520  if (arg.node._visible) {
1521  if (typeof prop.shape._geom === 'undefined') {
1522  prop.shape._geom = JSROOT.GEO.createGeometry(prop.shape);
1523  this.accountGeom(prop.shape._geom, prop.shape._typename);
1524  }
1525 
1526  geom = prop.shape._geom;
1527 
1528  } else {
1529  if (this._dummy_material === undefined)
1530  this._dummy_material =
1531  new THREE.MeshLambertMaterial( { transparent: true, opacity: 0, wireframe: false,
1532  color: 'white', vertexColors: THREE.NoColors,
1533  overdraw: 0., depthWrite : false, depthTest: false, visible: false } );
1534 
1535  prop.material = this._dummy_material;
1536  }
1537 
1538  var has_childs = (chlds !== null) && (chlds.length > 0);
1539  var work_around = false;
1540 
1541  // this is only for debugging - test invertion of whole geometry
1542  if (arg.main && (this.options.scale !== null)) {
1543  if ((this.options.scale.x<0) || (this.options.scale.y<0) || (this.options.scale.z<0)) {
1544  prop.matrix.scale(this.options.scale);
1545  }
1546  }
1547 
1548  if (arg.node._visible && (geom !== null)) {
1549  geom = this.checkFlipping(arg.toplevel, prop.matrix, prop.shape, geom, has_childs);
1550  work_around = has_childs && (geom === null);
1551  }
1552 
1553  if (geom === null) geom = new THREE.Geometry();
1554 
1555  var mesh = new THREE.Mesh( geom, prop.material );
1556 
1557  mesh.applyMatrix(prop.matrix);
1558 
1559  this.accountMesh(mesh);
1560 
1561  mesh.name = arg.node.fName;
1562 
1563  // add the mesh to the scene
1564  arg.toplevel.add(mesh);
1565 
1566  mesh.updateMatrixWorld();
1567 
1568  if (work_around) {
1569  JSROOT.console('perform workaroud for flipping mesh with childs');
1570 
1571  prop.matrix.identity(); // set to 1
1572 
1573  geom = this.checkFlipping(mesh, prop.matrix, prop.shape, prop.shape._geom, false);
1574 
1575  var dmesh = new THREE.Mesh( geom, prop.material );
1576 
1577  dmesh.applyMatrix(prop.matrix);
1578 
1579  dmesh.name = "..";
1580 
1581  // add the mesh to the scene
1582  mesh.add(dmesh);
1583 
1584  dmesh.updateMatrixWorld();
1585  }
1586 
1587  if (this.options._debug && (arg.node._visible || this.options._full)) {
1588  var helper = new THREE.WireframeHelper(mesh);
1589  helper.material.color.set(prop.fillcolor);
1590  helper.material.linewidth = ('fVolume' in arg.node) ? arg.node.fVolume.fLineWidth : 1;
1591  arg.toplevel.add(helper);
1592  }
1593 
1594  if (this.options._bound && (arg.node._visible || this.options._full)) {
1595  var boxHelper = new THREE.BoxHelper( mesh );
1596  arg.toplevel.add( boxHelper );
1597  }
1598 
1599  arg.mesh = mesh;
1600 
1601  if ((chlds === null) || (chlds.length == 0)) {
1602  // do not draw childs
1603  this._stack.pop();
1604  } else {
1605  arg.nchild = 0; // specify that childs should be extracted
1606  }
1607 
1608  return true;
1609  }
1610 
1611  JSROOT.TGeoPainter.prototype.NodeKind = function(obj) {
1612  if ((obj === undefined) || (obj === null) || (typeof obj !== 'object')) return -1;
1613  return ('fShape' in obj) && ('fTrans' in obj) ? 1 : 0;
1614  }
1615 
1616  JSROOT.TGeoPainter.prototype.CountGeoVolumes = function(obj, arg, lvl) {
1617  // count number of volumes, numver per hierarchy level, reference count, number of childs
1618  // also check if volume shape can be drawn
1619 
1620  var kind = this.NodeKind(obj);
1621  if (kind < 0) return 0;
1622 
1623  if (lvl === undefined) {
1624  lvl = 0;
1625  if (!arg) arg = { erase: true };
1626  if (!('map' in arg)) arg.map = [];
1627  arg.viscnt = 0;
1628  if (!('clear' in arg))
1629  arg.clear = function() {
1630  for (var n=0;n<this.map.length;++n) {
1631  delete this.map[n]._refcnt;
1632  delete this.map[n]._numchld;
1633  delete this.map[n]._visible;
1634  }
1635  this.map = [];
1636  this.viscnt = 0;
1637  };
1638  }
1639 
1640  var chlds = null, shape = null, vis = false;
1641  if (kind === 0) {
1642  if ((obj.fVolume === undefined) || (obj.fVolume === null)) return 0;
1643  shape = obj.fVolume.fShape;
1644  chlds = (obj.fVolume.fNodes !== null) ? obj.fVolume.fNodes.arr : null;
1645  vis = JSROOT.TestGeoAttBit(obj.fVolume, JSROOT.EGeoVisibilityAtt.kVisOnScreen)
1646  || ((lvl < arg.maxlvl) && JSROOT.TestGeoAttBit(obj.fVolume, JSROOT.EGeoVisibilityAtt.kVisThis));
1647  } else {
1648  if (obj.fShape === undefined) return 0;
1649  shape = obj.fShape;
1650  chlds = (obj.fElements !== null) ? obj.fElements.arr : null;
1651  vis = obj['fRnrSelf'];
1652  }
1653 
1654  if ('cnt' in arg) {
1655  if (arg.cnt[lvl] === undefined) arg.cnt[lvl] = 0;
1656  arg.cnt[lvl] += 1;
1657  }
1658 
1659  if ('_refcnt' in obj) {
1660  obj._refcnt++;
1661  } else {
1662  obj._refcnt = 1;
1663  obj._numchld = 0;
1664  arg.map.push(obj);
1665 
1666  /*
1667  // kVisNone : JSROOT.BIT(1), // the volume/node is invisible, as well as daughters
1668  // kVisThis : JSROOT.BIT(2), // this volume/node is visible
1669  // kVisDaughters : JSROOT.BIT(3), // all leaves are visible
1670  // kVisOneLevel : JSROOT.BIT(4), // first level daughters are visible
1671 
1672  if (JSROOT.TestGeoAttBit(obj.fVolume, JSROOT.EGeoVisibilityAtt.kVisNone))
1673  console.log('not visible');
1674  else
1675  if (JSROOT.TestGeoAttBit(obj.fVolume, JSROOT.EGeoVisibilityAtt.kVisOneLevel))
1676  console.log('only one level');
1677  }
1678  */
1679 
1680  if (vis && !('_visible' in obj) && (shape!==null)) {
1681  obj._visible = true;
1682  arg.viscnt++;
1683  }
1684 
1685  if (chlds !== null)
1686  for (var i = 0; i < chlds.length; ++i)
1687  obj._numchld += this.CountGeoVolumes(chlds[i], arg, lvl+1);
1688  }
1689 
1690  if ((lvl === 0) && arg.erase) arg.clear();
1691 
1692  return 1 + obj._numchld;
1693  }
1694 
1695  JSROOT.TGeoPainter.prototype.SameMaterial = function(node1, node2) {
1696 
1697  if ((node1===null) || (node2===null)) return node1 === node2;
1698 
1699  if (node1.fVolume.fLineColor >= 0)
1700  return (node1.fVolume.fLineColor === node2.fVolume.fLineColor);
1701 
1702  var m1 = (node1.fVolume['fMedium'] !== null) ? node1.fVolume['fMedium']['fMaterial'] : null;
1703  var m2 = (node2.fVolume['fMedium'] !== null) ? node2.fVolume['fMedium']['fMaterial'] : null;
1704 
1705  if (m1 === m2) return true;
1706 
1707  if ((m1 === null) || (m2 === null)) return false;
1708 
1709  return (m1.fFillStyle === m2.fFillStyle) && (m1.fFillColor === m2.fFillColor);
1710  }
1711 
1712  JSROOT.TGeoPainter.prototype.ScanUniqueVisVolumes = function(obj, lvl, arg) {
1713  if ((obj === undefined) || (obj===null) || (typeof obj !== 'object') ||
1714  (obj.fVolume === undefined) || (obj.fVolume == null)) return 0;
1715 
1716  if (lvl === 0) {
1717  arg.master = null;
1718  arg.vis_unique = true;
1719  arg.vis_master = null; // master used to verify material attributes
1720  arg.same_material = true;
1721  }
1722 
1723  var res = obj._visible ? 1 : 0;
1724 
1725  if (obj._refcnt > 1) arg.vis_unique = false;
1726  if (arg.master!==null)
1727  if (!this.SameMaterial(arg.master, obj)) arg.same_material = false;
1728 
1729  var top_unique = arg.vis_unique;
1730  arg.vis_unique = true;
1731 
1732  var top_master = arg.master, top_same = arg.same_material;
1733 
1734  arg.master = obj._visible ? obj : null;
1735  arg.same_material = true;
1736 
1737  var arr = (obj.fVolume.fNodes !== null) ? obj.fVolume.fNodes.arr : null;
1738 
1739  var numvis = 0;
1740  if (arr !== null)
1741  for (var i = 0; i < arr.length; ++i)
1742  numvis += this.ScanUniqueVisVolumes(arr[i], lvl+1, arg);
1743 
1744  obj._numvis = numvis;
1745  obj._visunique = arg.vis_unique;
1746  obj._samematerial = arg.same_material;
1747 
1748  if (obj._samematerial) {
1749  if (top_same && (top_master!=null) && (arg.master!==null))
1750  arg.same_material = this.SameMaterial(top_master, arg.master);
1751  else
1752  arg.same_material = top_same;
1753 
1754  if (top_master !== null) arg.master = top_master;
1755  } else {
1756  arg.master = null; // when material differ, no need to preserve master
1757  arg.same_material = false;
1758  }
1759 
1760  arg.vis_unique = top_unique && obj._visunique;
1761 
1762  return res + numvis;
1763  }
1764 
1765  JSROOT.TGeoPainter.prototype.createScene = function(webgl, w, h, pixel_ratio) {
1766  // three.js 3D drawing
1767  this._scene = new THREE.Scene();
1768  this._scene.fog = new THREE.Fog(0xffffff, 500, 300000);
1769 
1770  this._scene_width = w;
1771  this._scene_height = h;
1772 
1773  this._camera = new THREE.PerspectiveCamera(25, w / h, 1, 100000);
1774 
1775  this._renderer = webgl ?
1776  new THREE.WebGLRenderer({ antialias : true, logarithmicDepthBuffer: true,
1777  preserveDrawingBuffer: true }) :
1778  new THREE.CanvasRenderer({antialias : true });
1779  this._renderer.setPixelRatio(pixel_ratio);
1780  this._renderer.setClearColor(0xffffff, 1);
1781  this._renderer.setSize(w, h);
1782 
1783  var pointLight = new THREE.PointLight(0xefefef);
1784  this._camera.add( pointLight );
1785  pointLight.position.set(10, 10, 10);
1786  this._camera.up = this.options._yup ? new THREE.Vector3(0,1,0) : new THREE.Vector3(0,0,1);
1787  this._scene.add( this._camera );
1788 
1789  this._toplevel = new THREE.Object3D();
1790 
1791  this._scene.add(this._toplevel);
1792 
1793  this._overall_size = 10;
1794  }
1795 
1796 
1797  JSROOT.TGeoPainter.prototype.startDrawGeometry = function() {
1798  if (this.MatchObjectType("TGeoNode")) {
1799  this._nodedraw = true;
1800  this._stack = [ { toplevel: this._toplevel, node: this.GetObject(), main: true } ];
1801  }
1802  else if (this.MatchObjectType('TEveGeoShapeExtract')) {
1803  this._nodedraw = false;
1804  this._stack = [ { toplevel: this._toplevel, node: this.GetObject(), main: true } ];
1805  }
1806 
1807  this.accountClear();
1808  }
1809 
1810  JSROOT.TGeoPainter.prototype.adjustCameraPosition = function() {
1811 
1812  var box = new THREE.Box3().setFromObject(this._toplevel);
1813 
1814  var sizex = box.max.x - box.min.x,
1815  sizey = box.max.y - box.min.y,
1816  sizez = box.max.z - box.min.z,
1817  midx = (box.max.x + box.min.x)/2,
1818  midy = (box.max.y + box.min.y)/2,
1819  midz = (box.max.z + box.min.z)/2;
1820 
1821  this._overall_size = 2 * Math.max( sizex, sizey, sizez);
1822 
1823  this._camera.near = this._overall_size / 500;
1824  this._camera.far = this._overall_size * 500;
1825  this._camera.updateProjectionMatrix();
1826 
1827 // if (this.options._yup)
1828 // this._camera.position.set(midx-this._overall_size, midy+this._overall_size, midz-this._overall_size);
1829 // else
1830 // this._camera.position.set(midx-this._overall_size, midy-this._overall_size, midz+this._overall_size);
1831 
1832  if (this.options._yup)
1833  this._camera.position.set(midx-2*Math.max(sizex,sizez), midy+2*sizey, midz-2*Math.max(sizex,sizez));
1834  else
1835  this._camera.position.set(midx-2*Math.max(sizex,sizey), midy-2*Math.max(sizex,sizey), midz+2*sizez);
1836 
1837 
1838  this._lookat = new THREE.Vector3(midx, midy, midz);
1839  this._camera.lookAt(this._lookat);
1840 
1841  if (this._controls !== null) {
1842  this._controls.target.copy(this._lookat);
1843  this._controls.update();
1844  }
1845  }
1846 
1847  JSROOT.TGeoPainter.prototype.completeScene = function() {
1848  if ( this.options._debug || this.options._grid ) {
1849  if ( this.options._full ) {
1850  var boxHelper = new THREE.BoxHelper(this._toplevel);
1851  this._scene.add( boxHelper );
1852  }
1853  this._scene.add( new THREE.AxisHelper( 2 * this._overall_size ) );
1854  this._scene.add( new THREE.GridHelper( Math.ceil( this._overall_size), Math.ceil( this._overall_size ) / 50 ) );
1855  this.helpText("<font face='verdana' size='1' color='red'><center>Transform Controls<br>" +
1856  "'T' translate | 'R' rotate | 'S' scale<br>" +
1857  "'+' increase size | '-' decrease size<br>" +
1858  "'W' toggle wireframe/solid display<br>"+
1859  "keep 'Ctrl' down to snap to grid</center></font>");
1860  }
1861  }
1862 
1863  JSROOT.TGeoPainter.prototype.drawCount = function() {
1864 
1865  var tm1 = new Date();
1866 
1867  var arg = { cnt : [], maxlvl: -1 };
1868  var cnt = this.CountGeoVolumes(this.GetObject(), arg);
1869 
1870  var res = 'Total number: ' + cnt + '<br/>';
1871  for (var lvl=0;lvl<arg.cnt.length;++lvl) {
1872  if (arg.cnt[lvl] !== 0)
1873  res += (' lvl' + lvl + ': ' + arg.cnt[lvl] + '<br/>');
1874  }
1875  res += "Unique volumes: " + arg.map.length + '<br/>';
1876 
1877  if (arg.viscnt === 0) {
1878  arg.clear(); arg.maxlvl = 9999;
1879  cnt = this.CountGeoVolumes(this.GetObject(), arg);
1880  }
1881 
1882  res += "Visible volumes: " + arg.viscnt + '<br/>';
1883 
1884  if (cnt<200000) {
1885  this.ScanUniqueVisVolumes(this.GetObject(), 0, arg);
1886 
1887  for (var n=0;n<arg.map.length;++n)
1888  if (arg.map[n]._refcnt > 1) {
1889  res += (arg.map[n]._visible ? "vis" : "map") + n + " " + arg.map[n].fName + " nref:"+arg.map[n]._refcnt +
1890  ' chld:'+ arg.map[n]._numvis + "(" + arg.map[n]._numchld + ')' +
1891  " unique:" + arg.map[n]._visunique + " same:" + arg.map[n]._samematerial;
1892 
1893  if (arg.map[n]._samematerial) {
1894  if (arg.map[n]._visunique && (arg.map[n]._numvis>0)) res+=" (can merge with childs in Worker)"; else
1895  if ((arg.map[n]._refcnt > 4) && (arg.map[n]._numvis>1)) res+=" (make sense merge in main thread)";
1896  }
1897 
1898  res += "<br/>";
1899  }
1900  }
1901 
1902  var tm2 = new Date();
1903 
1904  res += "Elapsed time: " + (tm2.getTime() - tm1.getTime()) + "ms <br/>";
1905 
1906  this.select_main().style('overflow', 'auto').html(res);
1907 
1908  return this.DrawingReady();
1909  }
1910 
1911 
1912  JSROOT.TGeoPainter.prototype.DrawGeometry = function(opt) {
1913  if (typeof opt !== 'string') opt = "";
1914 
1915  if (opt === 'count')
1916  return this.drawCount();
1917 
1918  var size = this.size_for_3d();
1919 
1920  this.options = this.decodeOptions(opt);
1921 
1922  if (!('_yup' in this.options))
1923  this.options._yup = this.svg_canvas().empty();
1924 
1925  this._webgl = JSROOT.Painter.TestWebGL();
1926 
1927  this._data = { cnt: [], maxlvl : this.options.maxlvl }; // now count volumes which should go to the processing
1928 
1929  var total = this.CountGeoVolumes(this.GetObject(), this._data);
1930 
1931  // if no any volume was selected, probably it is because of visibility flags
1932  if ((total>0) && (this._data.viscnt == 0) && (this.options.maxlvl < 0)) {
1933  this._data.clear();
1934  this._data.maxlvl = 1111;
1935  total = this.CountGeoVolumes(this.GetObject(), this._data);
1936  }
1937 
1938  var maxlimit = this._webgl ? 1e7 : 1e4;
1939 
1940  if ((this._data.maxlvl === 1111) && (total > maxlimit)) {
1941  var sum = 0;
1942  for (var lvl=1; lvl < this._data.cnt.length; ++lvl) {
1943  sum += this._data.cnt.cnt[lvl];
1944  if (sum > maxlimit) {
1945  this._data.maxlvl = lvl - 1;
1946  this._data.clear();
1947  this.CountGeoVolumes(this.GetObject(), this._data);
1948  break;
1949  }
1950  }
1951  }
1952 
1953  this.createScene(this._webgl, size.width, size.height, window.devicePixelRatio);
1954 
1955  this.add_3d_canvas(size, this._renderer.domElement);
1956 
1957  this.startDrawGeometry();
1958 
1959  this._startm = new Date().getTime();
1960 
1961  this._drawcnt = 0; // counter used to build meshes
1962 
1963  this.CreateToolbar( { container: this.select_main().node() } );
1964 
1965  return this.continueDraw();
1966  }
1967 
1968  JSROOT.TGeoPainter.prototype.continueDraw = function() {
1969  var curr = new Date().getTime();
1970 
1971  var log = "";
1972 
1973  while(true) {
1974  if (this.drawNode()) {
1975  this._drawcnt++;
1976  log = "Creating meshes " + this._drawcnt;
1977  } else
1978  break;
1979 
1980  var now = new Date().getTime();
1981 
1982  if (now - curr > 300) {
1983  JSROOT.progress(log);
1984  setTimeout(this.continueDraw.bind(this), 0);
1985  return this;
1986  }
1987 
1988  // stop creation, render as is
1989  if (now - this._startm > 1e5) break;
1990  }
1991 
1992  var t2 = new Date().getTime();
1993  JSROOT.console('Create tm = ' + (t2-this._startm) + ' geom ' + this._num_geom + ' vertices ' + this._num_vertices + ' faces ' + this._num_faces + ' meshes ' + this._num_meshes);
1994 
1995  if (t2 - this._startm > 300) {
1996  JSROOT.progress('Rendering geometry');
1997  setTimeout(this.completeDraw.bind(this, true), 0);
1998  return this;
1999  }
2000 
2001  return this.completeDraw();
2002  }
2003 
2004  JSROOT.TGeoPainter.prototype.Render3D = function(tmout) {
2005  if (tmout === undefined) tmout = 5; // by default, rendering happens with timeout
2006 
2007  if (tmout <= 0) {
2008  if ('render_tmout' in this)
2009  clearTimeout(this['render_tmout']);
2010 
2011  var tm1 = new Date();
2012 
2013  // do rendering, most consuming time
2014  this._renderer.render(this._scene, this._camera);
2015 
2016  var tm2 = new Date();
2017 
2018  delete this['render_tmout'];
2019 
2020  if (this.first_render_tm === 0) {
2021  this.first_render_tm = tm2.getTime() - tm1.getTime();
2022  JSROOT.console('First render tm = ' + this.first_render_tm);
2023  this.addControls();
2024  }
2025 
2026  return;
2027  }
2028 
2029  // no need to shoot rendering once again
2030  if ('render_tmout' in this) return;
2031 
2032  this['render_tmout'] = setTimeout(this.Render3D.bind(this,0), tmout);
2033  }
2034 
2035  JSROOT.TGeoPainter.prototype.completeDraw = function(close_progress) {
2036 
2037  this.adjustCameraPosition();
2038 
2039  this.completeScene();
2040 
2041  if (this.options._axis) {
2042  var axis = JSROOT.Create("TNamed");
2043  axis._typename = "TAxis3D";
2044  axis._main = this;
2045  JSROOT.draw(this.divid, axis); // it will include drawing of
2046  }
2047 
2048  this.Render3D();
2049 
2050  if (close_progress) JSROOT.progress();
2051 
2052  this._data.clear();
2053 
2054  // pointer used in the event handlers
2055  var pthis = this;
2056  var dom = this.select_main().node();
2057 
2058  if (dom !== null) {
2059  dom.tabIndex = 0;
2060  dom.focus();
2061  dom.onkeypress = function(e) {
2062  if (!e) e = event;
2063  switch ( e.keyCode ) {
2064  case 87: // W
2065  case 119: // w
2066  pthis.toggleWireFrame(pthis._scene);
2067  break;
2068  }
2069  };
2070  dom.onclick = function(e) {
2071  dom.focus();
2072  };
2073  }
2074 
2075  return this.DrawingReady();
2076  }
2077 
2078 
2079  JSROOT.TGeoPainter.prototype.Cleanup = function(first_time) {
2080 
2081  if (first_time === undefined) {
2082  this.helpText();
2083  if (this._scene !== null)
2084  this.deleteChildren(this._scene);
2085  if ( this._tcontrols !== null)
2086  this._tcontrols.dispose();
2087  if (this._controls !== null)
2088  this._controls.dispose();
2089  }
2090 
2091  this._scene = null;
2092  this._scene_width = 0;
2093  this._scene_height = 0;
2094  this._renderer = null;
2095  this._toplevel = null;
2096  this._stack = null;
2097  this._camera = null;
2098 
2099  this.first_render_tm = 0;
2100 
2101  this._controls = null;
2102  this._tcontrols = null;
2103  this._toolbar = null;
2104  }
2105 
2106  JSROOT.TGeoPainter.prototype.helpText = function(msg) {
2107  JSROOT.progress(msg);
2108  }
2109 
2110  JSROOT.TGeoPainter.prototype.CheckResize = function() {
2111 
2112  var pad_painter = this.pad_painter();
2113 
2114  // firefox is the only browser which correctly supports resize of embedded canvas,
2115  // for others we should force canvas redrawing at every step
2116  if (pad_painter)
2117  if (!pad_painter.CheckCanvasResize(size, JSROOT.browser.isFirefox ? false : true)) return false;
2118 
2119  var size3d = this.size_for_3d();
2120 
2121  if ((this._scene_width === size3d.width) && (this._scene_height === size3d.height)) return false;
2122  if ((size3d.width<10) || (size3d.height<10)) return;
2123 
2124  this._scene_width = size3d.width;
2125  this._scene_height = size3d.height;
2126 
2127  this._camera.aspect = this._scene_width / this._scene_height;
2128  this._camera.updateProjectionMatrix();
2129 
2130  this._renderer.setSize( this._scene_width, this._scene_height );
2131 
2132  this.Render3D();
2133 
2134  return true;
2135  }
2136 
2137  JSROOT.TGeoPainter.prototype.ownedByTransformControls = function(child) {
2138  var obj = child.parent;
2139  while (obj && !(obj instanceof THREE.TransformControls) ) {
2140  obj = obj.parent;
2141  }
2142  return (obj && (obj instanceof THREE.TransformControls));
2143  }
2144 
2145  JSROOT.TGeoPainter.prototype.toggleWireFrame = function(obj) {
2146  var painter = this;
2147 
2148  var f = function(obj2) {
2149  if ( obj2.hasOwnProperty("material") && !(obj2 instanceof THREE.GridHelper) ) {
2150  if (!painter.ownedByTransformControls(obj2))
2151  obj2.material.wireframe = !obj2.material.wireframe;
2152  }
2153  }
2154  obj.traverse(f);
2155  this.Render3D();
2156  }
2157 
2158  JSROOT.TGeoPainter.prototype.deleteChildren = function(obj) {
2159  if ((typeof obj['children'] != 'undefined') && (obj['children'] instanceof Array)) {
2160  for ( var i=obj.children.length-1; i>=0; i-- ) {
2161  var ob = obj.children[i];
2162  this.deleteChildren(ob);
2163  try {
2164  obj.remove(obj.children[i]);
2165  } catch(e) {}
2166  try {
2167  ob.geometry.dispose();
2168  ob.geometry = null;
2169  } catch(e) {}
2170  try {
2171  ob.material.dispose();
2172  ob.material = null;
2173  } catch(e) {}
2174  try {
2175  ob.texture.dispose();
2176  ob.texture = null;
2177  } catch(e) {}
2178  ob = null;
2179  obj.children[i] = null;
2180  }
2181  obj.children = null;
2182  }
2183  obj = null;
2184  }
2185 
2186  JSROOT.Painter.drawGeometry = function(divid, geometry, opt) {
2187 
2188  // create painter and add it to canvas
2189  JSROOT.extend(this, new JSROOT.TGeoPainter(geometry));
2190 
2191  this.SetDivId(divid, 5);
2192 
2193  return this.DrawGeometry(opt);
2194  }
2195 
2196  JSROOT.Painter.drawGeoObject = function(divid, obj, opt) {
2197  if (obj === null) return this.DrawingReady();
2198 
2199  var node = null;
2200 
2201  if (('fShapeBits' in obj) && ('fShapeId' in obj)) {
2202  node = JSROOT.Create("TEveGeoShapeExtract");
2203  JSROOT.extend(node, { fTrans:null, fShape: obj, fRGBA: [ 0, 1, 0, 1], fElements: null, fRnrSelf: true });
2204  } else
2205  if ((obj._typename === 'TGeoVolumeAssembly') || (obj._typename === 'TGeoVolume'))
2206  node = obj;
2207 
2208  if (node !== null) {
2209  JSROOT.extend(this, new JSROOT.TGeoPainter(node));
2210  this.SetDivId(divid, 5);
2211  return this.DrawGeometry(opt);
2212  }
2213 
2214  return this.DrawingReady();
2215  }
2216 
2217  // ===================================================================================
2218 
2219  JSROOT.Painter.drawAxis3D = function(divid, axis, opt) {
2220 
2221  var painter = new JSROOT.TObjectPainter(axis);
2222 
2223  if (!('_main' in axis))
2224  painter.SetDivId(divid);
2225 
2226  painter['Draw3DAxis'] = function() {
2227  var main = this.main_painter();
2228 
2229  if ((main === null) && ('_main' in this.GetObject()))
2230  main = this.GetObject()._main; // simple workaround to get geo painter
2231 
2232  if ((main === null) || (main._toplevel === undefined))
2233  return console.warn('no geo object found for 3D axis drawing');
2234 
2235  var box = new THREE.Box3().setFromObject(main._toplevel);
2236 
2237  this.xmin = box.min.x; this.xmax = box.max.x;
2238  this.ymin = box.min.y; this.ymax = box.max.y;
2239  this.zmin = box.min.z; this.zmax = box.max.z;
2240 
2241  this.options = { Logx: false, Logy: false, Logz: false };
2242 
2243  this.size3d = 0; // use min/max values directly as graphical coordinates
2244 
2245  this['DrawXYZ'] = JSROOT.Painter.HPainter_DrawXYZ;
2246 
2247  this.toplevel = main._toplevel;
2248 
2249  this.DrawXYZ();
2250 
2251  main.adjustCameraPosition();
2252 
2253  main.Render3D();
2254  }
2255 
2256  painter.Draw3DAxis();
2257 
2258  return painter.DrawingReady();
2259  }
2260 
2261  // ===============================================================================
2262 
2263  JSROOT.expandGeoList = function(item, lst) {
2264  if ((lst==null) || !('arr' in lst) || (lst.arr.length==0)) return;
2265 
2266  item['_more'] = true;
2267  item['_geolst'] = lst;
2268 
2269  item['_get'] = function(item, itemname, callback) {
2270  if ('_geolst' in item)
2271  JSROOT.CallBack(callback, item, item._geolst);
2272 
2273  if ('_geoobj' in item)
2274  return JSROOT.CallBack(callback, item, item._geoobj);
2275 
2276  JSROOT.CallBack(callback, item, null);
2277  }
2278  item['_expand'] = function(node, lst) {
2279  // only childs
2280  if (!('arr' in lst)) return false;
2281 
2282  node['_childs'] = [];
2283 
2284  for (var n in lst.arr) {
2285  var obj = lst.arr[n];
2286  var sub = {
2287  _kind : "ROOT." + obj._typename,
2288  _name : obj.fName,
2289  _title : obj.fTitle,
2290  _parent : node,
2291  _geoobj : obj
2292  };
2293 
2294  if (obj._typename == "TGeoMaterial") sub._icon = "img_geomaterial"; else
2295  if (obj._typename == "TGeoMedium") sub._icon = "img_geomedium"; else
2296  if (obj._typename == "TGeoMixture") sub._icon = "img_geomixture";
2297 
2298  node['_childs'].push(sub);
2299  }
2300 
2301  return true;
2302  }
2303  };
2304 
2305  JSROOT.provideGeoVisStyle = function(volume) {
2306  var res = "";
2307 
2308  if (JSROOT.TestGeoAttBit(volume, JSROOT.EGeoVisibilityAtt.kVisThis))
2309  res += " geovis_this";
2310 
2311  if (JSROOT.TestGeoAttBit(volume, JSROOT.EGeoVisibilityAtt.kVisDaughters))
2312  res += " geovis_daughters";
2313 
2314  return res;
2315  }
2316 
2317  JSROOT.provideGeoMenu = function(menu, item, hpainter) {
2318  if (! ('_volume' in item)) return false;
2319 
2320  menu.add("separator");
2321  var vol = item._volume;
2322 
2323  function ToggleMenuBit(arg) {
2324  JSROOT.ToggleGeoAttBit(vol, arg);
2325  item._icon = item._icon.split(" ")[0] + JSROOT.provideGeoVisStyle(vol);
2326  hpainter.UpdateTreeNode(item);
2327  }
2328 
2329  menu.addchk(JSROOT.TestGeoAttBit(vol, JSROOT.EGeoVisibilityAtt.kVisNone), "Invisible",
2330  JSROOT.EGeoVisibilityAtt.kVisNone, ToggleMenuBit);
2331  menu.addchk(JSROOT.TestGeoAttBit(vol, JSROOT.EGeoVisibilityAtt.kVisThis), "Visible",
2332  JSROOT.EGeoVisibilityAtt.kVisThis, ToggleMenuBit);
2333  menu.addchk(JSROOT.TestGeoAttBit(vol, JSROOT.EGeoVisibilityAtt.kVisDaughters), "Daughters",
2334  JSROOT.EGeoVisibilityAtt.kVisDaughters, ToggleMenuBit);
2335  menu.addchk(JSROOT.TestGeoAttBit(vol, JSROOT.EGeoVisibilityAtt.kVisOneLevel), "1lvl daughters",
2336  JSROOT.EGeoVisibilityAtt.kVisOneLevel, ToggleMenuBit);
2337 
2338  return true;
2339  }
2340 
2341  JSROOT.geoIconClick = function(hitem) {
2342  if ((hitem==null) || (hitem._volume == null)) return false;
2343  JSROOT.ToggleGeoAttBit(hitem._volume, JSROOT.EGeoVisibilityAtt.kVisDaughters);
2344  hitem._icon = hitem._icon.split(" ")[0] + JSROOT.provideGeoVisStyle(hitem._volume);
2345  return true; // hpainter.UpdateTreeNode(hitem);
2346  }
2347 
2348  JSROOT.getGeoShapeIcon = function(shape) {
2349  switch (shape._typename) {
2350  case "TGeoArb8" : return "img_geoarb8"; break;
2351  case "TGeoCone" : return "img_geocone"; break;
2352  case "TGeoConeSeg" : return "img_geoconeseg"; break;
2353  case "TGeoCompositeShape" : return "img_geocomposite"; break;
2354  case "TGeoTube" : return "img_geotube"; break;
2355  case "TGeoTubeSeg" : return "img_geotubeseg"; break;
2356  case "TGeoPara" : return "img_geopara"; break;
2357  case "TGeoParaboloid" : return "img_geoparab"; break;
2358  case "TGeoPcon" : return "img_geopcon"; break;
2359  case "TGeoPgon" : return "img_geopgon"; break;
2360  case "TGeoShapeAssembly" : return "img_geoassembly"; break;
2361  case "TGeoSphere" : return "img_geosphere"; break;
2362  case "TGeoTorus" : return "img_geotorus"; break;
2363  case "TGeoTrd1" : return "img_geotrd1"; break;
2364  case "TGeoTrd2" : return "img_geotrd2"; break;
2365  case "TGeoXtru" : return "img_geoxtru"; break;
2366  case "TGeoTrap" : return "img_geotrap"; break;
2367  case "TGeoGtra" : return "img_geogtra"; break;
2368  case "TGeoEltu" : return "img_geoeltu"; break;
2369  case "TGeoHype" : return "img_geohype"; break;
2370  case "TGeoCtub" : return "img_geoctub"; break;
2371  }
2372  return "img_geotube";
2373  }
2374 
2375  JSROOT.expandGeoShape = function(parent, shape, itemname) {
2376  var item = {
2377  _kind : "ROOT." + shape._typename,
2378  _name : itemname,
2379  _title : shape._typename,
2380  _icon : JSROOT.getGeoShapeIcon(shape),
2381  _parent : parent,
2382  _shape : shape,
2383  _get : function(item, itemname, callback) {
2384  if ((item!==null) && ('_shape' in item))
2385  return JSROOT.CallBack(callback, item, item._shape);
2386  JSROOT.CallBack(callback, item, null);
2387  }
2388  };
2389 
2390  if (!('_childs' in parent)) parent['_childs'] = [];
2391  parent._childs.push(item);
2392  return true;
2393  }
2394 
2395  JSROOT.expandGeoVolume = function(parent, volume, arg) {
2396 
2397  if ((parent == null) || (volume==null)) return false;
2398 
2399  // avoid duplication
2400  if ('_childs' in parent)
2401  for (var n=0;n<parent._childs.length;++n)
2402  if (volume === parent._childs[n]._volume) return true;
2403 
2404  var item = {
2405  _kind : "ROOT.TGeoVolume",
2406  _name : (arg!=null) ? arg : volume.fName,
2407  _title : volume.fTitle,
2408  _parent : parent,
2409  _volume : volume, // keep direct reference
2410  _more : (volume.fNodes !== undefined) && (volume.fNodes !== null),
2411  _menu : JSROOT.provideGeoMenu,
2412  _icon_click : JSROOT.geoIconClick,
2413  _get : function(item, itemname, callback) {
2414  if ((item!=null) && ('_volume' in item))
2415  return JSROOT.CallBack(callback, item, item._volume);
2416 
2417  JSROOT.CallBack(callback, item, null);
2418  }
2419  };
2420 
2421  if (item['_more']) {
2422  item['_expand'] = function(node, obj) {
2423  var subnodes = obj.fNodes.arr;
2424  for (var i in subnodes)
2425  JSROOT.expandGeoVolume(node, subnodes[i].fVolume);
2426  return true;
2427  }
2428  } else
2429  if ((volume.fShape !== null) && (volume.fShape._typename === "TGeoCompositeShape") && (volume.fShape.fNode !== null)) {
2430  item['_more'] = true;
2431  item['_expand'] = function(node, obj) {
2432  JSROOT.expandGeoShape(node, obj.fShape.fNode.fLeft, 'Left');
2433  JSROOT.expandGeoShape(node, obj.fShape.fNode.fRight, 'Right');
2434  return true;
2435  }
2436  }
2437 
2438  if (item._title == "")
2439  if (volume._typename != "TGeoVolume") item._title = volume._typename;
2440 
2441  if (volume.fShape !== null) {
2442  if (item._title == "")
2443  item._title = volume.fShape._typename;
2444 
2445  item._icon = JSROOT.getGeoShapeIcon(volume.fShape);
2446  }
2447 
2448  if (!('_childs' in parent)) parent['_childs'] = [];
2449 
2450  if (!('_icon' in item))
2451  item._icon = item['_more'] ? "img_geocombi" : "img_geobbox";
2452 
2453  item._icon += JSROOT.provideGeoVisStyle(volume);
2454 
2455  // avoid name duplication of the items
2456  for (var cnt=0;cnt<1000000;cnt++) {
2457  var curr_name = item._name;
2458  if (curr_name.length == 0) curr_name = "item";
2459  if (cnt>0) curr_name+= "_"+cnt;
2460  // avoid name duplication
2461  for (var n in parent['_childs']) {
2462  if (parent['_childs'][n]['_name'] == curr_name) {
2463  curr_name = ""; break;
2464  }
2465  }
2466  if (curr_name.length > 0) {
2467  if (cnt>0) item._name = curr_name;
2468  break;
2469  }
2470  }
2471 
2472  parent['_childs'].push(item);
2473 
2474  return true;
2475  }
2476 
2477  JSROOT.expandGeoManagerHierarchy = function(hitem, obj) {
2478  if ((hitem==null) || (obj==null)) return false;
2479 
2480  hitem['_childs'] = [];
2481 
2482  var item1 = { _name : "Materials", _kind : "Folder", _title : "list of materials" };
2483  JSROOT.expandGeoList(item1, obj.fMaterials);
2484  hitem['_childs'].push(item1);
2485 
2486  var item2 = { _name : "Media", _kind : "Folder", _title : "list of media" };
2487  JSROOT.expandGeoList(item2, obj.fMedia);
2488  hitem['_childs'].push(item2);
2489 
2490  var item3 = { _name : "Tracks", _kind : "Folder", _title : "list of tracks" };
2491  JSROOT.expandGeoList(item3, obj.fTracks);
2492  hitem['_childs'].push(item3);
2493 
2494  JSROOT.expandGeoVolume(hitem, obj.fMasterVolume, "Volume");
2495 
2496  return true;
2497  }
2498 
2499  JSROOT.addDrawFunc({ name: "TGeoVolumeAssembly", icon: 'img_geoassembly', func: JSROOT.Painter.drawGeometry, expand: "JSROOT.expandGeoVolume", opt : "all;count;limit;maxlvl2" });
2500  JSROOT.addDrawFunc({ name: "TAxis3D", func: JSROOT.Painter.drawAxis3D });
2501 
2502  return JSROOT.Painter;
2503 
2504 }));
2505