otsdaq_utilities  v2_05_02_indev
JSRootGeoPainter.js
1 
4 (function( factory ) {
5  if ( typeof define === "function" && define.amd ) {
6  define( [ 'JSRootPainter', 'd3', 'threejs', 'JSRoot3DPainter', 'JSRootGeoBase', 'dat.gui' ], factory );
7  } else if (typeof exports === 'object' && typeof module !== 'undefined') {
8  var jsroot = require("./JSRootCore.js");
9  factory(jsroot, require("d3"), require("three"), require("./JSRoot3DPainter.js"), require("./JSRootGeoBase.js"),
10  undefined, jsroot.nodejs || (typeof document=='undefined') ? jsroot.nodejs_document : document);
11  } else {
12  if (typeof JSROOT == 'undefined')
13  throw new Error('JSROOT is not defined', 'JSRootGeoPainter.js');
14  if (typeof JSROOT.Painter != 'object')
15  throw new Error('JSROOT.Painter is not defined', 'JSRootGeoPainter.js');
16  if (typeof d3 == 'undefined')
17  throw new Error('d3 is not defined', 'JSRootGeoPainter.js');
18  if (typeof THREE == 'undefined')
19  throw new Error('THREE is not defined', 'JSRootGeoPainter.js');
20  factory( JSROOT, d3, THREE );
21  }
22 } (function( JSROOT, d3, THREE, _3d, _geo, _dat, document ) {
23 
24  "use strict";
25 
26  JSROOT.sources.push("geom");
27 
28  if ((typeof document=='undefined') && (typeof window=='object')) document = window.document;
29 
30  if ((typeof define === "function") && define.amd)
31  JSROOT.loadScript('$$$style/JSRootGeoPainter.css');
32 
33  if (typeof JSROOT.GEO !== 'object')
34  console.error('JSROOT.GEO namespace is not defined')
35 
36  // ============================================================================================
37 
38  function Toolbar(container, buttons, bright) {
39  this.bright = bright;
40  if ((container !== undefined) && (typeof container.append == 'function')) {
41  this.element = container.append("div").attr('class','jsroot');
42  this.addButtons(buttons);
43  }
44  }
45 
46  Toolbar.prototype.addButtons = function(buttons) {
47  var pthis = this;
48 
49  this.buttonsNames = [];
50  buttons.forEach(function(buttonGroup) {
51  var group = pthis.element.append('div').attr('class', 'toolbar-group');
52 
53  buttonGroup.forEach(function(buttonConfig) {
54  var buttonName = buttonConfig.name;
55  if (!buttonName) {
56  throw new Error('must provide button \'name\' in button config');
57  }
58  if (pthis.buttonsNames.indexOf(buttonName) !== -1) {
59  throw new Error('button name \'' + buttonName + '\' is taken');
60  }
61  pthis.buttonsNames.push(buttonName);
62 
63  pthis.createButton(group, buttonConfig);
64  });
65  });
66  };
67 
68  Toolbar.prototype.createButton = function(group, config) {
69 
70  var title = config.title;
71  if (title === undefined) title = config.name;
72 
73  if (typeof config.click !== 'function')
74  throw new Error('must provide button \'click\' function in button config');
75 
76  var button = group.append('a')
77  .attr('class', this.bright ? 'toolbar-btn-bright' : 'toolbar-btn')
78  .attr('rel', 'tooltip')
79  .attr('data-title', title)
80  .on('click', config.click);
81 
82  this.createIcon(button, config.icon || JSROOT.ToolbarIcons.question);
83  }
84 
85 
86  Toolbar.prototype.changeBrightness = function(bright) {
87  this.bright = bright;
88  if (!this.element) return;
89 
90  this.element.selectAll(bright ? '.toolbar-btn' : ".toolbar-btn-bright")
91  .attr("class", !bright ? 'toolbar-btn' : "toolbar-btn-bright");
92  }
93 
94 
95  Toolbar.prototype.createIcon = function(button, thisIcon) {
96  var dimensions = thisIcon.size ? thisIcon.size.split(' ') : [512, 512],
97  width = dimensions[0],
98  height = dimensions[1] || dimensions[0],
99  scale = thisIcon.scale || 1,
100  svg = button.append("svg:svg")
101  .attr('height', '1em')
102  .attr('width', '1em')
103  .attr('viewBox', [0, 0, width, height].join(' '));
104 
105  if ('recs' in thisIcon) {
106  var rec = {};
107  for (var n=0;n<thisIcon.recs.length;++n) {
108  JSROOT.extend(rec, thisIcon.recs[n]);
109  svg.append('rect').attr("x", rec.x).attr("y", rec.y)
110  .attr("width", rec.w).attr("height", rec.h)
111  .attr("fill", rec.f);
112  }
113  } else {
114  var elem = svg.append('svg:path').attr('d',thisIcon.path);
115  if (scale !== 1)
116  elem.attr('transform', 'scale(' + scale + ' ' + scale +')');
117  }
118  };
119 
120  Toolbar.prototype.Cleanup = function() {
121  if (this.element) {
122  this.element.remove();
123  delete this.element;
124  }
125  };
126 
133  function TGeoPainter(obj) {
134 
135  if (obj && (obj._typename === "TGeoManager")) {
136  this.geo_manager = obj;
137  obj = obj.fMasterVolume;
138  }
139 
140  if (obj && (obj._typename.indexOf('TGeoVolume') === 0))
141  obj = { _typename:"TGeoNode", fVolume: obj, fName: obj.fName, $geoh: obj.$geoh, _proxy: true };
142 
143  JSROOT.TObjectPainter.call(this, obj);
144 
145  this.no_default_title = true; // do not set title to main DIV
146  this.mode3d = true; // indication of 3D mode
147  this.drawing_stage = 0; //
148  this.ctrl = {
149  clipIntersect: true,
150  clip: [{ name:"x", enabled: false, value: 0, min: -100, max: 100}, { name:"y", enabled: false, value: 0, min: -100, max: 100}, { name:"z", enabled: false, value: 0, min: -100, max: 100}],
151  ssao: { enabled: false, output: THREE.SSAOPass.OUTPUT.Default, kernelRadius: 0, minDistance: 0.001, maxDistance: 0.1 },
152  info: { num_meshes: 0, num_faces: 0, num_shapes: 0 },
153  highlight: false,
154  highlight_scene: false,
155  depthTest: true,
156  depthMethod: "dflt",
157  select_in_view: false,
158  update_browser: true,
159  light: { top: false, bottom: false, left: false, right: false, front: false, specular: true },
160  trans_radial: 0, trans_z: 0
161  };
162 
163  this.ctrl.depthMethodItems = [
164  {name: 'Default', value: "dflt"},
165  {name: 'Raytraicing', value: "ray"},
166  {name: 'Boundary box', value: "box"},
167  {name: 'Mesh size', value: "size"},
168  {name: 'Central point', value: "pnt" }
169  ];
170 
171  this.ctrl.ssao.outputItems = [
172  {name: 'Default', value: THREE.SSAOPass.OUTPUT.Default},
173  {name: 'SSAO Only', value: THREE.SSAOPass.OUTPUT.SSAO},
174  {name: 'SSAO Only + Blur', value: THREE.SSAOPass.OUTPUT.Blur},
175  {name: 'Beauty', value: THREE.SSAOPass.OUTPUT.Beauty},
176  {name: 'Depth', value: THREE.SSAOPass.OUTPUT.Depth},
177  {name: 'Normal', value: THREE.SSAOPass.OUTPUT.Normal}
178  ];
179 
180  this.Cleanup(true);
181  }
182 
183  TGeoPainter.prototype = Object.create( JSROOT.TObjectPainter.prototype );
184 
185  TGeoPainter.prototype.CreateToolbar = function(args) {
186  if (this._toolbar || this._usesvg || this._usesvgimg || this.ctrl.notoolbar) return;
187  var painter = this;
188  var buttonList = [{
189  name: 'toImage',
190  title: 'Save as PNG',
191  icon: JSROOT.ToolbarIcons.camera,
192  click: function() { painter.createSnapshot(); }
193  }, {
194  name: 'control',
195  title: 'Toggle control UI',
196  icon: JSROOT.ToolbarIcons.rect,
197  click: function() { painter.showControlOptions('toggle'); }
198  }, {
199  name: 'enlarge',
200  title: 'Enlarge geometry drawing',
201  icon: JSROOT.ToolbarIcons.circle,
202  click: function() { painter.toggleEnlarge(); }
203  }];
204 
205  // Only show VR icon if WebVR API available.
206  if (navigator.getVRDisplays) {
207  buttonList.push({
208  name: 'entervr',
209  title: 'Enter VR (It requires a VR Headset connected)',
210  icon: JSROOT.ToolbarIcons.vrgoggles,
211  click: function() { painter.toggleVRMode(); }
212  });
213  this.InitVRMode();
214  }
215 
216  if (JSROOT.gStyle.ContextMenu)
217  buttonList.push({
218  name: 'menu',
219  title: 'Show context menu',
220  icon: JSROOT.ToolbarIcons.question,
221  click: function() {
222 
223  d3.event.preventDefault();
224  d3.event.stopPropagation();
225 
226  var evnt = d3.event;
227 
228  if (!JSROOT.Painter.closeMenu())
229  JSROOT.Painter.createMenu(painter, function(menu) {
230  menu.painter.FillContextMenu(menu);
231  menu.show(evnt);
232  });
233  }
234  });
235 
236  var bkgr = new THREE.Color(this.ctrl.background);
237 
238  this._toolbar = new Toolbar( this.select_main(), [buttonList], (bkgr.r + bkgr.g + bkgr.b) < 1);
239  }
240 
241  TGeoPainter.prototype.InitVRMode = function() {
242  var pthis = this;
243  // Dolly contains camera and controllers in VR Mode
244  // Allows moving the user in the scene
245  this._dolly = new THREE.Group();
246  this._scene.add(this._dolly);
247  this._standingMatrix = new THREE.Matrix4();
248 
249  // Raycaster temp variables to avoid one per frame allocation.
250  this._raycasterEnd = new THREE.Vector3();
251  this._raycasterOrigin = new THREE.Vector3();
252 
253  navigator.getVRDisplays().then(function (displays) {
254  var vrDisplay = displays[0];
255  if (!vrDisplay) return;
256  pthis._renderer.vr.setDevice(vrDisplay);
257  pthis._vrDisplay = vrDisplay;
258  if (vrDisplay.stageParameters) {
259  pthis._standingMatrix.fromArray(vrDisplay.stageParameters.sittingToStandingTransform);
260  }
261  pthis.InitVRControllersGeometry();
262  });
263  }
264 
265  TGeoPainter.prototype.InitVRControllersGeometry = function() {
266 
267  var geometry = new THREE.SphereGeometry(0.025, 18, 36);
268  var material = new THREE.MeshBasicMaterial({color: 'grey'});
269  var rayMaterial = new THREE.MeshBasicMaterial({color: 'fuchsia'});
270  var rayGeometry = new THREE.BoxBufferGeometry(0.001, 0.001, 2);
271  var ray1Mesh = new THREE.Mesh(rayGeometry, rayMaterial);
272  var ray2Mesh = new THREE.Mesh(rayGeometry, rayMaterial);
273  var sphere1 = new THREE.Mesh(geometry, material);
274  var sphere2 = new THREE.Mesh(geometry, material);
275 
276  this._controllersMeshes = [];
277  this._controllersMeshes.push(sphere1);
278  this._controllersMeshes.push(sphere2);
279  ray1Mesh.position.z -= 1;
280  ray2Mesh.position.z -= 1;
281  sphere1.add(ray1Mesh);
282  sphere2.add(ray2Mesh);
283  this._dolly.add(sphere1);
284  this._dolly.add(sphere2);
285  // Controller mesh hidden by default
286  sphere1.visible = false;
287  sphere2.visible = false;
288  }
289 
290  TGeoPainter.prototype.UpdateVRControllersList = function() {
291  var gamepads = navigator.getGamepads && navigator.getGamepads();
292  // Has controller list changed?
293  if (this.vrControllers && (gamepads.length === this.vrControllers.length)) { return; }
294  // Hide meshes.
295  this._controllersMeshes.forEach(function (mesh) { mesh.visible = false; });
296  this._vrControllers = [];
297  for (var i = 0; i < gamepads.length; ++i) {
298  if (!gamepads[i] || !gamepads[i].pose) { continue; }
299  this._vrControllers.push({
300  gamepad: gamepads[i],
301  mesh: this._controllersMeshes[i]
302  });
303  this._controllersMeshes[i].visible = true;
304  }
305  }
306 
307  TGeoPainter.prototype.ProcessVRControllerIntersections = function() {
308  var intersects = []
309  for (var i = 0; i < this._vrControllers.length; ++i) {
310  var controller = this._vrControllers[i].mesh;
311  var end = controller.localToWorld(this._raycasterEnd.set(0, 0, -1));
312  var origin = controller.localToWorld(this._raycasterOrigin.set(0, 0, 0));
313  end.sub(origin).normalize();
314  intersects = intersects.concat(this._controls.GetOriginDirectionIntersects(origin, end));
315  }
316  // Remove duplicates.
317  intersects = intersects.filter(function (item, pos) {return intersects.indexOf(item) === pos});
318  this._controls.ProcessMouseMove(intersects);
319  }
320 
321  TGeoPainter.prototype.UpdateVRControllers = function() {
322  this.UpdateVRControllersList();
323  // Update pose.
324  for (var i = 0; i < this._vrControllers.length; ++i) {
325  var controller = this._vrControllers[i];
326  var orientation = controller.gamepad.pose.orientation;
327  var position = controller.gamepad.pose.position;
328  var controllerMesh = controller.mesh;
329  if (orientation) { controllerMesh.quaternion.fromArray(orientation); }
330  if (position) { controllerMesh.position.fromArray(position); }
331  controllerMesh.updateMatrix();
332  controllerMesh.applyMatrix(this._standingMatrix);
333  controllerMesh.matrixWorldNeedsUpdate = true;
334  }
335  this.ProcessVRControllerIntersections();
336  }
337 
338  TGeoPainter.prototype.toggleVRMode = function() {
339  if (!this._vrDisplay) return;
340  // Toggle VR mode off
341  if (this._vrDisplay.isPresenting) {
342  this.ExitVRMode();
343  return;
344  }
345  var pthis = this;
346  this._previousCameraPosition = this._camera.position.clone();
347  this._previousCameraRotation = this._camera.rotation.clone();
348  this._vrDisplay.requestPresent([{ source: this._renderer.domElement }]).then(function() {
349  pthis._previousCameraNear = pthis._camera.near;
350  pthis._dolly.position.set(pthis._camera.position.x/4, - pthis._camera.position.y/8, - pthis._camera.position.z/4);
351  pthis._camera.position.set(0,0,0);
352  pthis._dolly.add(pthis._camera);
353  pthis._camera.near = 0.1;
354  pthis._camera.updateProjectionMatrix();
355  pthis._renderer.vr.enabled = true;
356  pthis._renderer.setAnimationLoop(function () {
357  pthis.UpdateVRControllers();
358  pthis.Render3D(0);
359  });
360  });
361  this._renderer.vr.enabled = true;
362 
363  window.addEventListener( 'keydown', function ( event ) {
364  // Esc Key turns VR mode off
365  if (event.keyCode === 27) pthis.ExitVRMode();
366  });
367  }
368 
369  TGeoPainter.prototype.ExitVRMode = function() {
370  var pthis = this;
371  if (!this._vrDisplay.isPresenting) return;
372  this._renderer.vr.enabled = false;
373  this._dolly.remove(this._camera);
374  this._scene.add(this._camera);
375  // Restore Camera pose
376  this._camera.position.copy(this._previousCameraPosition);
377  this._previousCameraPosition = undefined;
378  this._camera.rotation.copy(this._previousCameraRotation);
379  this._previousCameraRotation = undefined;
380  this._camera.near = this._previousCameraNear;
381  this._camera.updateProjectionMatrix();
382  this._vrDisplay.exitPresent();
383  }
384 
385  TGeoPainter.prototype.GetGeometry = function() {
386  return this.GetObject();
387  }
388 
389  TGeoPainter.prototype.ModifyVisisbility = function(name, sign) {
390  if (JSROOT.GEO.NodeKind(this.GetGeometry()) !== 0) return;
391 
392  if (name == "")
393  return JSROOT.GEO.SetBit(this.GetGeometry().fVolume, JSROOT.GEO.BITS.kVisThis, (sign === "+"));
394 
395  var regexp, exact = false;
396 
397  //arg.node.fVolume
398  if (name.indexOf("*") < 0) {
399  regexp = new RegExp("^"+name+"$");
400  exact = true;
401  } else {
402  regexp = new RegExp("^" + name.split("*").join(".*") + "$");
403  exact = false;
404  }
405 
406  this.FindNodeWithVolume(regexp, function(arg) {
407  JSROOT.GEO.InvisibleAll.call(arg.node.fVolume, (sign !== "+"));
408  return exact ? arg : null; // continue search if not exact expression provided
409  });
410  }
411 
412  TGeoPainter.prototype.decodeOptions = function(opt) {
413  if (typeof opt != "string") opt = "";
414 
415  var res = { _grid: false, _bound: false, _debug: false,
416  _full: false, _axis: 0,
417  _count: false, wireframe: false,
418  scale: new THREE.Vector3(1,1,1), zoom: 1.0, rotatey: 0, rotatez: 0,
419  more: 1, maxlimit: 100000,
420  vislevel: undefined, maxnodes: undefined, dflt_colors: false,
421  use_worker: false, show_controls: false,
422  highlight: false, highlight_scene: false, no_screen: false,
423  project: '', is_main: false, tracks: false, showtop: false, can_rotate: true, ortho_camera: false,
424  clipx: false, clipy: false, clipz: false, usessao: false, outline: false,
425  script_name: "", transparency: 0, rotate: false, background: '#FFFFFF',
426  depthMethod: "dflt", mouse_tmout: 50, trans_radial: 0, trans_z: 0 };
427 
428  var _opt = JSROOT.GetUrlOption('_grid');
429  if (_opt !== null && _opt == "true") res._grid = true;
430  var _opt = JSROOT.GetUrlOption('_debug');
431  if (_opt !== null && _opt == "true") { res._debug = true; res._grid = true; }
432  if (_opt !== null && _opt == "bound") { res._debug = true; res._grid = true; res._bound = true; }
433  if (_opt !== null && _opt == "full") { res._debug = true; res._grid = true; res._full = true; res._bound = true; }
434 
435  var macro = opt.indexOf("macro:");
436  if (macro>=0) {
437  var separ = opt.indexOf(";", macro+6);
438  if (separ<0) separ = opt.length;
439  res.script_name = opt.substr(macro+6,separ-macro-6);
440  opt = opt.substr(0, macro) + opt.substr(separ+1);
441  console.log('script', res.script_name, 'rest', opt);
442  }
443 
444  while (true) {
445  var pp = opt.indexOf("+"), pm = opt.indexOf("-");
446  if ((pp<0) && (pm<0)) break;
447  var p1 = pp, sign = "+";
448  if ((p1<0) || ((pm>=0) && (pm<pp))) { p1 = pm; sign = "-"; }
449 
450  var p2 = p1+1, regexp = new RegExp('[,; .]');
451  while ((p2<opt.length) && !regexp.test(opt[p2]) && (opt[p2]!='+') && (opt[p2]!='-')) p2++;
452 
453  var name = opt.substring(p1+1, p2);
454  opt = opt.substr(0,p1) + opt.substr(p2);
455  // console.log("Modify visibility", sign,':',name);
456 
457  this.ModifyVisisbility(name, sign);
458  }
459 
460  var d = new JSROOT.DrawOptions(opt);
461 
462  if (d.check("MAIN")) res.is_main = true;
463 
464  if (d.check("TRACKS")) res.tracks = true; // only for TGeoManager
465  if (d.check("SHOWTOP")) res.showtop = true; // only for TGeoManager
466  if (d.check("NO_SCREEN")) res.no_screen = true; // use kVisOnScreen bits as visibility
467 
468  if (d.check("ORTHO_CAMERA_ROTATE")) { res.ortho_camera = true; res.can_rotate = true; }
469  if (d.check("ORTHO_CAMERA")) { res.ortho_camera = true; res.can_rotate = false; }
470 
471  if (d.check("DEPTHRAY") || d.check("DRAY")) res.depthMethod = "ray";
472  if (d.check("DEPTHBOX") || d.check("DBOX")) res.depthMethod = "box";
473  if (d.check("DEPTHPNT") || d.check("DPNT")) res.depthMethod = "pnt";
474  if (d.check("DEPTHSIZE") || d.check("DSIZE")) res.depthMethod = "size";
475  if (d.check("DEPTHDFLT") || d.check("DDFLT")) res.depthMethod = "dflt";
476 
477  if (d.check("ZOOM", true)) res.zoom = d.partAsFloat(0, 100) / 100;
478  if (d.check("ROTY", true)) res.rotatey = d.partAsFloat();
479  if (d.check("ROTZ", true)) res.rotatez = d.partAsFloat();
480  if (d.check("VISLVL", true)) res.vislevel = d.partAsInt();
481 
482  if (d.check('BLACK')) res.background = "#000000";
483  if (d.check('WHITE')) res.background = "#FFFFFF";
484 
485  if (d.check('BKGR_', true)) {
486  var bckgr = null;
487  if (d.partAsInt(1)>0) bckgr = JSROOT.Painter.root_colors[d.partAsInt()]; else
488  for (var col=0;col<8;++col)
489  if (JSROOT.Painter.root_colors[col].toUpperCase() === d.part) bckgr = JSROOT.Painter.root_colors[col];
490  if (bckgr) res.background = "#" + new THREE.Color(bckgr).getHexString();
491  }
492 
493  if (d.check("MORE3")) res.more = 3;
494  if (d.check("MORE")) res.more = 2;
495  if (d.check("ALL")) { res.more = 10; res.vislevel = 9; }
496 
497  if (d.check("CONTROLS") || d.check("CTRL")) res.show_controls = true;
498 
499  if (d.check("CLIPXYZ")) res.clipx = res.clipy = res.clipz = true;
500  if (d.check("CLIPX")) res.clipx = true;
501  if (d.check("CLIPY")) res.clipy = true;
502  if (d.check("CLIPZ")) res.clipz = true;
503  if (d.check("CLIP")) res.clipx = res.clipy = res.clipz = true;
504 
505  if (d.check("PROJX", true)) { res.project = 'x'; if (d.partAsInt(1)>0) res.projectPos = d.partAsInt(); res.can_rotate = false; }
506  if (d.check("PROJY", true)) { res.project = 'y'; if (d.partAsInt(1)>0) res.projectPos = d.partAsInt(); res.can_rotate = false; }
507  if (d.check("PROJZ", true)) { res.project = 'z'; if (d.partAsInt(1)>0) res.projectPos = d.partAsInt(); res.can_rotate = false; }
508 
509  if (d.check("DFLT_COLORS") || d.check("DFLT")) res.dflt_colors = true;
510  if (d.check("SSAO")) res.usessao = true;
511  if (d.check("OUTLINE")) res.outline = true;
512 
513  if (d.check("NOWORKER")) res.use_worker = -1;
514  if (d.check("WORKER")) res.use_worker = 1;
515 
516  if (d.check("NOHIGHLIGHT") || d.check("NOHIGH")) res.highlight_scene = res.highlight = 0;
517  if (d.check("HIGHLIGHT")) res.highlight_scene = res.highlight = true;
518  if (d.check("HSCENEONLY")) { res.highlight_scene = true; res.highlight = 0; }
519  if (d.check("NOHSCENE")) res.highlight_scene = 0;
520  if (d.check("HSCENE")) res.highlight_scene = true;
521 
522  if (d.check("WIREFRAME") || d.check("WIRE")) res.wireframe = true;
523  if (d.check("ROTATE")) res.rotate = true;
524 
525  if (d.check("INVX") || d.check("INVERTX")) res.scale.x = -1;
526  if (d.check("INVY") || d.check("INVERTY")) res.scale.y = -1;
527  if (d.check("INVZ") || d.check("INVERTZ")) res.scale.z = -1;
528 
529  if (d.check("COUNT")) res._count = true;
530 
531  if (d.check('TRANSP',true))
532  res.transparency = d.partAsInt(0,100)/100;
533 
534  if (d.check('OPACITY',true))
535  res.transparency = 1 - d.partAsInt(0,100)/100;
536 
537  if (d.check("AXISCENTER") || d.check("AC")) res._axis = 2;
538 
539  if (d.check('TRR',true)) res.trans_radial = d.partAsInt()/100;
540  if (d.check('TRZ',true)) res.trans_z = d.partAsInt()/100;
541 
542  if (d.check("AXIS") || d.check("A")) res._axis = true;
543 
544  if (d.check("D")) res._debug = true;
545  if (d.check("G")) res._grid = true;
546  if (d.check("B")) res._bound = true;
547  if (d.check("W")) res.wireframe = true;
548  if (d.check("F")) res._full = true;
549  if (d.check("Y")) res._yup = true;
550  if (d.check("Z")) res._yup = false;
551 
552  // when drawing geometry without TCanvas, yup = true by default
553  if (res._yup === undefined)
554  res._yup = this.svg_canvas().empty();
555 
556  return res;
557  }
558 
559  TGeoPainter.prototype.ActivateInBrowser = function(names, force) {
560  // if (this.GetItemName() === null) return;
561 
562  if (typeof names == 'string') names = [ names ];
563 
564  if (this._hpainter) {
565  // show browser if it not visible
566  this._hpainter.activate(names, force);
567 
568  // if highlight in the browser disabled, suppress in few seconds
569  if (!this.ctrl.update_browser)
570  setTimeout(this._hpainter.activate.bind(this._hpainter, []), 2000);
571  }
572  }
573 
574  TGeoPainter.prototype.TestMatrixes = function() {
575  // method can be used to check matrix calculations with current three.js model
576 
577  var painter = this, errcnt = 0, totalcnt = 0, totalmax = 0;
578 
579  var arg = {
580  domatrix: true,
581  func: function(node) {
582 
583  var m2 = this.getmatrix();
584 
585  var entry = this.CopyStack();
586 
587  var mesh = painter._clones.CreateObject3D(entry.stack, painter._toplevel, 'mesh');
588 
589  if (!mesh) return true;
590 
591  totalcnt++;
592 
593  var m1 = mesh.matrixWorld, flip, origm2;
594 
595  if (m1.equals(m2)) return true
596  if ((m1.determinant()>0) && (m2.determinant()<-0.9)) {
597  flip = THREE.Vector3(1,1,-1);
598  origm2 = m2;
599  m2 = m2.clone().scale(flip);
600  if (m1.equals(m2)) return true;
601  }
602 
603  var max = 0;
604  for (var k=0;k<16;++k)
605  max = Math.max(max, Math.abs(m1.elements[k] - m2.elements[k]));
606 
607  totalmax = Math.max(max, totalmax);
608 
609  if (max < 1e-4) return true;
610 
611  console.log(painter._clones.ResolveStack(entry.stack).name, 'maxdiff', max, 'determ', m1.determinant(), m2.determinant());
612 
613  errcnt++;
614 
615  return false;
616  }
617  };
618 
619 
620  tm1 = new Date().getTime();
621 
622  var cnt = this._clones.ScanVisible(arg);
623 
624  tm2 = new Date().getTime();
625 
626  console.log('Compare matrixes total',totalcnt,'errors',errcnt, 'takes', tm2-tm1, 'maxdiff', totalmax);
627  }
628 
629 
630  TGeoPainter.prototype.FillContextMenu = function(menu) {
631  menu.add("header: Draw options");
632 
633  menu.addchk(this.ctrl.update_browser, "Browser update", function() {
634  this.ctrl.update_browser = !this.ctrl.update_browser;
635  if (!this.ctrl.update_browser) this.ActivateInBrowser([]);
636  });
637  menu.addchk(this.ctrl.show_controls, "Show Controls", function() {
638  this.showControlOptions('toggle');
639  });
640  menu.addchk(this.ctrl._axis, "Show axes", function() {
641  this.setAxesDraw('toggle');
642  });
643  if (this.geo_manager)
644  menu.addchk(this.ctrl.showtop, "Show top volume", function() {
645  this.setShowTop(!this.ctrl.showtop);
646  });
647 
648  menu.addchk(this.ctrl.wireframe, "Wire frame", function() {
649  this.toggleWireFrame();
650  });
651  menu.addchk(this.ctrl.highlight, "Highlight volumes", function() {
652  this.ctrl.highlight = !this.ctrl.highlight;
653  });
654  menu.addchk(this.ctrl.highlight_scene, "Highlight scene", function() {
655  this.ctrl.highlight_scene = !this.ctrl.highlight_scene;
656  });
657  menu.add("Reset camera position", function() {
658  this.focusCamera();
659  });
660  menu.add("Get camera position", function() {
661  alert("Position (as url): &opt=" + this.produceCameraUrl());
662  });
663  if (!this.ctrl.project)
664  menu.addchk(this.ctrl.rotate, "Autorotate", function() {
665  this.setAutoRotate(!this.ctrl.rotate);
666  });
667  menu.addchk(this.ctrl.select_in_view, "Select in view", function() {
668  this.ctrl.select_in_view = !this.ctrl.select_in_view;
669  if (this.ctrl.select_in_view) this.startDrawGeometry();
670  });
671  }
672 
675  TGeoPainter.prototype.changedGlobalTransparency = function(transparency, skip_render) {
676  var func = (typeof transparency == 'function') ? transparency : null;
677  if (func || (transparency === undefined)) transparency = this.ctrl.transparency;
678  this._toplevel.traverse( function (node) {
679  if (node && node.material && (node.material.inherentOpacity !== undefined)) {
680  var t = func ? func(node) : undefined;
681  if (t !== undefined)
682  node.material.opacity = 1 - t;
683  else
684  node.material.opacity = Math.min(1 - (transparency || 0), node.material.inherentOpacity);
685  node.material.transparent = node.material.opacity < 1;
686  }
687  });
688  if (!skip_render) this.Render3D(-1);
689  }
690 
692  TGeoPainter.prototype.resetTransformation = function() {
693  this.changedTransformation("reset");
694  }
695 
697  TGeoPainter.prototype.changedTransformation = function(arg) {
698  if (!this._toplevel) return;
699 
700  var ctrl = this.ctrl,
701  translation = new THREE.Matrix4(),
702  vect2 = new THREE.Vector3();
703 
704  if (arg == "reset")
705  ctrl.trans_z = ctrl.trans_radial = 0;
706 
707  this._toplevel.traverse(function(mesh) {
708  if (mesh.stack === undefined) return;
709 
710  var node = mesh.parent;
711 
712  if (arg == "reset") {
713  if (node.matrix0) {
714  node.matrix.copy(node.matrix0);
715  node.matrix.decompose( node.position, node.quaternion, node.scale );
716  node.matrixWorldNeedsUpdate = true;
717  }
718  delete node.matrix0;
719  delete node.vect0;
720  delete node.vect1;
721  delete node.minvert;
722  return;
723  }
724 
725  if (node.vect0 === undefined) {
726  node.matrix0 = node.matrix.clone();
727  node.minvert = new THREE.Matrix4().getInverse( node.matrixWorld );
728 
729  var box3 = JSROOT.GEO.getBoundingBox(mesh, null, true),
730  signz = mesh._flippedMesh ? -1 : 1;
731 
732  // real center of mesh in local coordinates
733  node.vect0 = new THREE.Vector3((box3.max.x + box3.min.x) / 2, (box3.max.y + box3.min.y) / 2, signz * (box3.max.z + box3.min.z) / 2).applyMatrix4(node.matrixWorld);
734  node.vect1 = new THREE.Vector3(0,0,0).applyMatrix4(node.minvert);
735  }
736 
737  vect2.set(ctrl.trans_radial * node.vect0.x, ctrl.trans_radial * node.vect0.y, ctrl.trans_z * node.vect0.z).applyMatrix4(node.minvert).sub(node.vect1);
738 
739  node.matrix.multiplyMatrices(node.matrix0, translation.makeTranslation(vect2.x, vect2.y, vect2.z));
740  node.matrix.decompose( node.position, node.quaternion, node.scale );
741  node.matrixWorldNeedsUpdate = true;
742  });
743 
744  this._toplevel.updateMatrixWorld();
745 
746  // axes drawing always triggers rendering
747  if (arg != "norender")
748  this.drawSimpleAxis();
749  }
750 
752  TGeoPainter.prototype.changedAutoRotate = function() {
753  this.autorotate(2.5);
754  }
755 
757  TGeoPainter.prototype.changedAxes = function() {
758  if (typeof this.ctrl._axis == 'string')
759  this.ctrl._axis = parseInt(this.ctrl._axis);
760 
761  this.drawSimpleAxis();
762  }
763 
765  TGeoPainter.prototype.changedBackground = function(val) {
766  if (val !== undefined) this.ctrl.background = val;
767  this._renderer.setClearColor(this.ctrl.background, 1);
768  this.Render3D(0);
769 
770  if (this._toolbar) {
771  var bkgr = new THREE.Color(this.ctrl.background);
772  this._toolbar.changeBrightness((bkgr.r + bkgr.g + bkgr.b) < 1);
773  }
774  }
775 
777  TGeoPainter.prototype.changedSSAO = function() {
778  if (!this.ctrl.ssao.enabled) {
779  this.removeSSAO();
780  } else {
781  this.createSSAO();
782 
783  this._ssaoPass.output = parseInt(this.ctrl.ssao.output);
784  this._ssaoPass.kernelRadius = this.ctrl.ssao.kernelRadius;
785  this._ssaoPass.minDistance = this.ctrl.ssao.minDistance;
786  this._ssaoPass.maxDistance = this.ctrl.ssao.maxDistance;
787  }
788 
789  this.updateClipping();
790  }
791 
793  TGeoPainter.prototype.showControlOptions = function(on) {
794  var usedat = _dat;
795  if (!usedat && (typeof dat == 'object'))
796  usedat = dat;
797 
798  if (on === 'load') {
799  if (typeof usedat == 'undefined')
800  throw new Error('dat.gui is not defined', 'JSRootGeoPainter.js');
801  on = true;
802  } else if (on === 'toggle') {
803  on = !this._datgui;
804  } else if (on === undefined) {
805  on = this.ctrl.show_controls;
806  }
807 
808  this.ctrl.show_controls = on;
809 
810  if (this._datgui) {
811  if (!on) {
812  d3.select(this._datgui.domElement).remove();
813  this._datgui.destroy();
814  delete this._datgui;
815  }
816  return;
817  }
818 
819  if (!on) return;
820 
821  if (typeof usedat == 'undefined')
822  return JSROOT.AssertPrerequisites("datgui", this.showControlOptions.bind(this,"load"));
823 
824  var painter = this;
825 
826  this._datgui = new usedat.GUI({ autoPlace: false, width: Math.min(650, painter._renderer.domElement.width / 2) });
827 
828  var main = this.select_main();
829  if (main.style('position')=='static') main.style('position','relative');
830 
831  d3.select(this._datgui.domElement)
832  .style('position','absolute')
833  .style('top',0).style('right',0);
834 
835  main.node().appendChild(this._datgui.domElement);
836 
837  this._datgui.painter = this;
838 
839  if (this.ctrl.project) {
840 
841  var bound = this.getGeomBoundingBox(this.getProjectionSource(), 0.01);
842 
843  var axis = this.ctrl.project;
844 
845  if (this.ctrl.projectPos === undefined)
846  this.ctrl.projectPos = (bound.min[axis] + bound.max[axis])/2;
847 
848  this._datgui.add(this.ctrl, 'projectPos', bound.min[axis], bound.max[axis])
849  .name(axis.toUpperCase() + ' projection')
850  .onChange(function (value) {
851  painter.startDrawGeometry();
852  });
853 
854  } else {
855  // Clipping Options
856 
857  var clipFolder = this._datgui.addFolder('Clipping'),
858  clip_handler = this.changedClipping.bind(this, -1);
859 
860  for (var naxis=0;naxis<3;++naxis) {
861  var cc = this.ctrl.clip[naxis],
862  axisC = cc.name.toUpperCase();
863 
864  clipFolder.add(cc, 'enabled')
865  .name('Enable ' + axisC)
866  .listen() // react if option changed outside
867  .onChange(clip_handler);
868 
869  clipFolder.add(cc, "value", cc.min, cc.max)
870  .name(axisC + ' position')
871  .onChange(this.changedClipping.bind(this, naxis));
872  }
873 
874  clipFolder.add(this.ctrl, 'clipIntersect').name("Clip intersection")
875  .listen().onChange(clip_handler);
876 
877  }
878 
879  // Appearance Options
880 
881  var appearance = this._datgui.addFolder('Appearance');
882 
883  appearance.add(this.ctrl, 'highlight').name('Highlight Selection')
884  .listen().onChange(this.changedHighlight.bind(this));
885 
886  appearance.add(this.ctrl, 'transparency', 0.0, 1.0, 0.001)
887  .listen().onChange(this.changedGlobalTransparency.bind(this));
888 
889  appearance.addColor(this.ctrl, 'background').name('Background')
890  .onChange(this.changedBackground.bind(this));
891 
892  appearance.add(this.ctrl, 'wireframe').name('Wireframe')
893  .listen().onChange(this.changedWireFrame.bind(this));
894 
895  this.ctrl._axis_cfg = 0;
896  appearance.add(this.ctrl, '_axis', { "none" : 0, "show": 1, "center": 2}).name('Axes')
897  .onChange(this.changedAxes.bind(this));
898 
899  if (!this.ctrl.project)
900  appearance.add(this.ctrl, 'rotate').name("Autorotate")
901  .listen().onChange(this.changedAutoRotate.bind(this));
902 
903  appearance.add(this, 'focusCamera').name('Reset camera position');
904 
905  // Advanced Options
906 
907  if (this._webgl) {
908  var advanced = this._datgui.addFolder('Advanced'), depthcfg = {};
909  this.ctrl.depthMethodItems.forEach(function(i) { depthcfg[i.name] = i.value; });
910 
911  advanced.add(this.ctrl, 'depthTest').name("Depth test")
912  .listen().onChange(this.changedDepthTest.bind(this));
913 
914  advanced.add( this.ctrl, 'depthMethod', depthcfg)
915  .name("Rendering order")
916  .onChange(this.changedDepthMethod.bind(this));
917 
918  advanced.add(this, 'resetAdvanced').name('Reset');
919  }
920 
921  // Transformation Options
922  if (!this.ctrl.project) {
923  var transform = this._datgui.addFolder('Transform');
924  transform.add(this.ctrl, 'trans_z', 0., 3., 0.01)
925  .name('Z axis')
926  .listen().onChange(this.changedTransformation.bind(this));
927  transform.add(this.ctrl, 'trans_radial', 0., 3., 0.01)
928  .name('Radial')
929  .listen().onChange(this.changedTransformation.bind(this));
930 
931  transform.add(this, 'resetTransformation').name('Reset');
932 
933  if (this.ctrl.trans_z || this.ctrl.trans_radial) transform.open();
934  }
935 
936  // no SSAO folder if outline is enabled
937  if (this.ctrl.outline) return;
938 
939  var ssaofolder = this._datgui.addFolder('Smooth Lighting (SSAO)'),
940  ssao_handler = this.changedSSAO.bind(this), ssaocfg = {};
941 
942  this.ctrl.ssao.outputItems.forEach(function(i) { ssaocfg[i.name] = i.value; });
943 
944  ssaofolder.add(this.ctrl.ssao, 'enabled').name('Enable SSAO')
945  .listen().onChange(ssao_handler);
946 
947  ssaofolder.add( this.ctrl.ssao, 'output', ssaocfg)
948  .listen().onChange(ssao_handler);
949 
950  ssaofolder.add( this.ctrl.ssao, 'kernelRadius', 0, 32)
951  .listen().onChange(ssao_handler);
952 
953  ssaofolder.add( this.ctrl.ssao, 'minDistance', 0.001, 0.02)
954  .listen().onChange(ssao_handler);
955 
956  ssaofolder.add( this.ctrl.ssao, 'maxDistance', 0.01, 0.3)
957  .listen().onChange(ssao_handler);
958  }
959 
960  TGeoPainter.prototype.removeSSAO = function() {
961  // we cannot remove pass from composer - just disable it
962  delete this._ssaoPass;
963  delete this._effectComposer;
964  }
965 
966  TGeoPainter.prototype.createSSAO = function() {
967  if (!this._webgl) return;
968 
969  // this._depthRenderTarget = new THREE.WebGLRenderTarget( this._scene_width, this._scene_height, { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter } );
970  // Setup SSAO pass
971  if (!this._ssaoPass) {
972  if (!this._effectComposer) {
973  this._effectComposer = new THREE.EffectComposer( this._renderer );
974  this._effectComposer.addPass( new THREE.RenderPass( this._scene, this._camera ) );
975  }
976 
977  this._ssaoPass = new THREE.SSAOPass( this._scene, this._camera, this._scene_width, this._scene_height );
978  this._ssaoPass.kernelRadius = 16;
979  this._ssaoPass.renderToScreen = true;
980 
981  // Add pass to effect composer
982  this._effectComposer.addPass( this._ssaoPass );
983  }
984  }
985 
986  TGeoPainter.prototype.OrbitContext = function(evnt, intersects) {
987 
988  JSROOT.Painter.createMenu(this, function(menu) {
989  var numitems = 0, numnodes = 0, cnt = 0;
990  if (intersects)
991  for (var n=0;n<intersects.length;++n) {
992  if (intersects[n].object.stack) numnodes++;
993  if (intersects[n].object.geo_name) numitems++;
994  }
995 
996  if (numnodes + numitems === 0) {
997  menu.painter.FillContextMenu(menu);
998  } else {
999  var many = (numnodes + numitems) > 1;
1000 
1001  if (many) menu.add("header:" + ((numitems > 0) ? "Items" : "Nodes"));
1002 
1003  for (var n=0;n<intersects.length;++n) {
1004  var obj = intersects[n].object,
1005  name, itemname, hdr;
1006 
1007  if (obj.geo_name) {
1008  itemname = obj.geo_name;
1009  if (itemname.indexOf("<prnt>") == 0)
1010  itemname = (menu.painter.GetItemName() || "top") + itemname.substr(6);
1011  name = itemname.substr(itemname.lastIndexOf("/")+1);
1012  if (!name) name = itemname;
1013  hdr = name;
1014  } else if (obj.stack) {
1015  name = menu.painter._clones.ResolveStack(obj.stack).name;
1016  itemname = menu.painter.GetStackFullName(obj.stack);
1017  hdr = menu.painter.GetItemName();
1018  if (name.indexOf("Nodes/") === 0) hdr = name.substr(6); else
1019  if (name.length > 0) hdr = name; else
1020  if (!hdr) hdr = "header";
1021 
1022  } else
1023  continue;
1024 
1025  menu.add((many ? "sub:" : "header:") + hdr, itemname, function(arg) { this.ActivateInBrowser([arg], true); });
1026 
1027  menu.add("Browse", itemname, function(arg) { this.ActivateInBrowser([arg], true); });
1028 
1029  if (menu.painter._hpainter)
1030  menu.add("Inspect", itemname, function(arg) { this._hpainter.display(itemname, "inspect"); });
1031 
1032  if (obj.geo_name) {
1033  menu.add("Hide", n, function(indx) {
1034  var mesh = intersects[indx].object;
1035  mesh.visible = false; // just disable mesh
1036  if (mesh.geo_object) mesh.geo_object.$hidden_via_menu = true; // and hide object for further redraw
1037  menu.painter.Render3D();
1038  });
1039 
1040  if (many) menu.add("endsub:");
1041 
1042  continue;
1043  }
1044 
1045  var wireframe = menu.painter.accessObjectWireFrame(obj);
1046 
1047  if (wireframe!==undefined)
1048  menu.addchk(wireframe, "Wireframe", n, function(indx) {
1049  var m = intersects[indx].object.material;
1050  m.wireframe = !m.wireframe;
1051  this.Render3D();
1052  });
1053 
1054  if (++cnt>1)
1055  menu.add("Manifest", n, function(indx) {
1056 
1057  if (this._last_manifest)
1058  this._last_manifest.wireframe = !this._last_manifest.wireframe;
1059 
1060  if (this._last_hidden)
1061  this._last_hidden.forEach(function(obj) { obj.visible = true; });
1062 
1063  this._last_hidden = [];
1064 
1065  for (var i=0;i<indx;++i)
1066  this._last_hidden.push(intersects[i].object);
1067 
1068  this._last_hidden.forEach(function(obj) { obj.visible = false; });
1069 
1070  this._last_manifest = intersects[indx].object.material;
1071 
1072  this._last_manifest.wireframe = !this._last_manifest.wireframe;
1073 
1074  this.Render3D();
1075  });
1076 
1077 
1078  menu.add("Focus", n, function(indx) {
1079  this.focusCamera(intersects[indx].object);
1080  });
1081 
1082  if (!menu.painter._geom_viewer)
1083  menu.add("Hide", n, function(indx) {
1084  var resolve = menu.painter._clones.ResolveStack(intersects[indx].object.stack);
1085 
1086  if (resolve.obj && (resolve.node.kind === 0) && resolve.obj.fVolume) {
1087  JSROOT.GEO.SetBit(resolve.obj.fVolume, JSROOT.GEO.BITS.kVisThis, false);
1088  JSROOT.GEO.updateBrowserIcons(resolve.obj.fVolume, this._hpainter);
1089  } else
1090  if (resolve.obj && (resolve.node.kind === 1)) {
1091  resolve.obj.fRnrSelf = false;
1092  JSROOT.GEO.updateBrowserIcons(resolve.obj, this._hpainter);
1093  }
1094  // intersects[arg].object.visible = false;
1095  // this.Render3D();
1096 
1097  this.testGeomChanges();// while many volumes may disappear, recheck all of them
1098  });
1099 
1100  if (many) menu.add("endsub:");
1101  }
1102  }
1103  menu.show(evnt);
1104  });
1105  }
1106 
1107  TGeoPainter.prototype.FilterIntersects = function(intersects) {
1108 
1109  if (!intersects.length) return intersects;
1110 
1111  // check redirections
1112  for (var n=0;n<intersects.length;++n)
1113  if (intersects[n].object.geo_highlight)
1114  intersects[n].object = intersects[n].object.geo_highlight;
1115 
1116  // remove all elements without stack - indicator that this is geometry object
1117  // also remove all objects which are mostly transparent
1118  for (var n=intersects.length-1; n>=0; --n) {
1119 
1120  var obj = intersects[n].object;
1121 
1122  var unique = (obj.stack !== undefined) || (obj.geo_name !== undefined);
1123 
1124  if (unique && obj.material && (obj.material.opacity !== undefined))
1125  unique = (obj.material.opacity >= 0.1);
1126 
1127  if (obj.jsroot_special) unique = false;
1128 
1129  for (var k=0;(k<n) && unique;++k)
1130  if (intersects[k].object === obj) unique = false;
1131 
1132  if (!unique) intersects.splice(n,1);
1133  }
1134 
1135  var clip = this.ctrl.clip;
1136 
1137  if (clip[0].enabled || clip[1].enabled || clip[2].enabled) {
1138  var clippedIntersects = [];
1139 
1140  function myXor(a,b) { return ( a && !b ) || (!a && b); }
1141 
1142  for (var i = 0; i < intersects.length; ++i) {
1143  var point = intersects[i].point, special = (intersects[i].object.type == "Points"), clipped = true;
1144 
1145  if (clip[0].enabled && myXor(this._clipPlanes[0].normal.dot(point) > this._clipPlanes[0].constant, special)) clipped = false;
1146  if (clip[1].enabled && myXor(this._clipPlanes[1].normal.dot(point) > this._clipPlanes[1].constant, special)) clipped = false;
1147  if (clip[2].enabled && (this._clipPlanes[2].normal.dot(point) > this._clipPlanes[2].constant)) clipped = false;
1148 
1149  if (!clipped) clippedIntersects.push(intersects[i]);
1150  }
1151 
1152  intersects = clippedIntersects;
1153  }
1154 
1155  return intersects;
1156  }
1157 
1158  TGeoPainter.prototype.testCameraPositionChange = function() {
1159  // function analyzes camera position and start redraw of geometry if
1160  // objects in view may be changed
1161 
1162  if (!this.ctrl.select_in_view || this._draw_all_nodes) return;
1163 
1164  var matrix = JSROOT.GEO.CreateProjectionMatrix(this._camera);
1165 
1166  var frustum = JSROOT.GEO.CreateFrustum(matrix);
1167 
1168  // check if overall bounding box seen
1169  if (!frustum.CheckBox(this.getGeomBoundingBox(this._toplevel)))
1170  this.startDrawGeometry();
1171  }
1172 
1173  TGeoPainter.prototype.ResolveStack = function(stack) {
1174  return this._clones && stack ? this._clones.ResolveStack(stack) : null;
1175  }
1176 
1177  TGeoPainter.prototype.GetStackFullName = function(stack) {
1178  var mainitemname = this.GetItemName(),
1179  sub = this.ResolveStack(stack);
1180  if (!sub || !sub.name) return mainitemname;
1181  return mainitemname ? (mainitemname + "/" + sub.name) : sub.name;
1182  }
1183 
1186  TGeoPainter.prototype.AddHighlightHandler = function(handler) {
1187  if (!handler || typeof handler.HighlightMesh != 'function') return;
1188  if (!this._highlight_handlers) this._highlight_handlers = [];
1189  this._highlight_handlers.push(handler);
1190  }
1191 
1193 
1194  function GeoDrawingControl(mesh) {
1195  JSROOT.Painter.InteractiveControl.call(this);
1196  this.mesh = (mesh && mesh.material) ? mesh : null;
1197  }
1198 
1199  GeoDrawingControl.prototype = Object.create(JSROOT.Painter.InteractiveControl.prototype);
1200 
1201  GeoDrawingControl.prototype.setHighlight = function(col, indx) {
1202  return this.drawSpecial(col, indx);
1203  }
1204 
1205  GeoDrawingControl.prototype.drawSpecial = function(col, indx) {
1206  var c = this.mesh;
1207  if (!c || !c.material) return;
1208 
1209  if (col) {
1210  if (!c.origin)
1211  c.origin = {
1212  color: c.material.color,
1213  opacity: c.material.opacity,
1214  width: c.material.linewidth,
1215  size: c.material.size
1216  };
1217  c.material.color = new THREE.Color( col );
1218  c.material.opacity = 1.;
1219  if (c.hightlightWidthScale && !JSROOT.browser.isWin)
1220  c.material.linewidth = c.origin.width * c.hightlightWidthScale;
1221  if (c.highlightScale)
1222  c.material.size = c.origin.size * c.highlightScale;
1223  return true;
1224  } else if (c.origin) {
1225  c.material.color = c.origin.color;
1226  c.material.opacity = c.origin.opacity;
1227  if (c.hightlightWidthScale)
1228  c.material.linewidth = c.origin.width;
1229  if (c.highlightScale)
1230  c.material.size = c.origin.size;
1231  return true;
1232  }
1233  }
1234 
1236 
1237  TGeoPainter.prototype.HighlightMesh = function(active_mesh, color, geo_object, geo_index, geo_stack, no_recursive) {
1238 
1239  if (geo_object) {
1240  active_mesh = active_mesh ? [ active_mesh ] : [];
1241  var extras = this.getExtrasContainer();
1242  if (extras)
1243  extras.traverse(function(obj3d) {
1244  if ((obj3d.geo_object === geo_object) && (active_mesh.indexOf(obj3d)<0)) active_mesh.push(obj3d);
1245  });
1246  } else if (geo_stack && this._toplevel) {
1247  active_mesh = [];
1248  this._toplevel.traverse(function(mesh) {
1249  if ((mesh instanceof THREE.Mesh) && JSROOT.GEO.IsSameStack(mesh.stack, geo_stack)) active_mesh.push(mesh);
1250  });
1251  } else {
1252  active_mesh = active_mesh ? [ active_mesh ] : [];
1253  }
1254 
1255  if (!active_mesh.length) active_mesh = null;
1256 
1257  if (active_mesh) {
1258  // check if highlight is disabled for correspondent objects kinds
1259  if (active_mesh[0].geo_object) {
1260  if (!this.ctrl.highlight_scene) active_mesh = null;
1261  } else {
1262  if (!this.ctrl.highlight) active_mesh = null;
1263  }
1264  }
1265 
1266  if (!no_recursive) {
1267  // check all other painters
1268 
1269  if (active_mesh) {
1270  if (!geo_object) geo_object = active_mesh[0].geo_object;
1271  if (!geo_stack) geo_stack = active_mesh[0].stack;
1272  }
1273 
1274  var lst = this._highlight_handlers || (!this._main_painter ? this._slave_painters : this._main_painter._slave_painters.concat([this._main_painter]));
1275 
1276  for (var k=0;k<lst.length;++k)
1277  if (lst[k]!==this) lst[k].HighlightMesh(null, color, geo_object, geo_index, geo_stack, true);
1278  }
1279 
1280  var curr_mesh = this._selected_mesh;
1281 
1282  function get_ctrl(mesh) {
1283  return mesh.get_ctrl ? mesh.get_ctrl() : new GeoDrawingControl(mesh);
1284  }
1285 
1286  // check if selections are the same
1287  if (!curr_mesh && !active_mesh) return false;
1288  var same = false;
1289  if (curr_mesh && active_mesh && (curr_mesh.length == active_mesh.length)) {
1290  same = true;
1291  for (var k=0;(k<curr_mesh.length) && same;++k) {
1292  if ((curr_mesh[k] !== active_mesh[k]) || get_ctrl(curr_mesh[k]).checkHighlightIndex(geo_index)) same = false;
1293  }
1294  }
1295  if (same) return !!curr_mesh;
1296 
1297  if (curr_mesh)
1298  for (var k=0;k<curr_mesh.length;++k)
1299  get_ctrl(curr_mesh[k]).setHighlight();
1300 
1301  this._selected_mesh = active_mesh;
1302 
1303  if (active_mesh)
1304  for (var k=0;k<active_mesh.length;++k)
1305  get_ctrl(active_mesh[k]).setHighlight(color || 0xffaa33, geo_index);
1306 
1307  this.Render3D(0);
1308 
1309  return !!active_mesh;
1310  }
1311 
1312  TGeoPainter.prototype.ProcessMouseClick = function(pnt, intersects, evnt) {
1313  if (!intersects.length) return;
1314 
1315  var mesh = intersects[0].object;
1316  if (!mesh.get_ctrl) return;
1317 
1318  var ctrl = mesh.get_ctrl();
1319 
1320  var click_indx = ctrl.extractIndex(intersects[0]);
1321 
1322  ctrl.evnt = evnt;
1323 
1324  if (ctrl.setSelected("blue", click_indx))
1325  this.Render3D();
1326 
1327  ctrl.evnt = null;
1328  }
1329 
1331  TGeoPainter.prototype.setMouseTmout = function(val) {
1332  if (this.ctrl)
1333  this.ctrl.mouse_tmout = val;
1334 
1335  if (this._controls)
1336  this._controls.mouse_tmout = val;
1337  }
1338 
1341  TGeoPainter.prototype.setDepthMethod = function(val) {
1342  if (this.ctrl)
1343  this.ctrl.depthMethod = val;
1344  }
1345 
1347  TGeoPainter.prototype.addOrbitControls = function() {
1348 
1349  if (this._controls || this._usesvg || JSROOT.BatchMode) return;
1350 
1351  var painter = this;
1352 
1353  this.SetTooltipAllowed(JSROOT.gStyle.Tooltip > 0);
1354 
1355  this._controls = JSROOT.Painter.CreateOrbitControl(this, this._camera, this._scene, this._renderer, this._lookat);
1356 
1357  this._controls.mouse_tmout = this.ctrl.mouse_tmout; // set larger timeout for geometry processing
1358 
1359  if (!this.ctrl.can_rotate) this._controls.enableRotate = false;
1360 
1361  this._controls.ContextMenu = this.OrbitContext.bind(this);
1362 
1363  this._controls.ProcessMouseMove = function(intersects) {
1364 
1365  var active_mesh = null, tooltip = null, resolve = null, names = [], geo_object, geo_index;
1366 
1367  // try to find mesh from intersections
1368  for (var k=0;k<intersects.length;++k) {
1369  var obj = intersects[k].object, info = null;
1370  if (!obj) continue;
1371  if (obj.geo_object) info = obj.geo_name; else
1372  if (obj.stack) info = painter.GetStackFullName(obj.stack);
1373  if (!info) continue;
1374 
1375  if (info.indexOf("<prnt>")==0)
1376  info = painter.GetItemName() + info.substr(6);
1377 
1378  names.push(info);
1379 
1380  if (!active_mesh) {
1381  active_mesh = obj;
1382  tooltip = info;
1383  geo_object = obj.geo_object;
1384  if (obj.get_ctrl) {
1385  geo_index = obj.get_ctrl().extractIndex(intersects[k]);
1386  if ((geo_index !== undefined) && (typeof tooltip == "string")) tooltip += " indx:" + JSON.stringify(geo_index);
1387  }
1388  if (active_mesh.stack) resolve = painter.ResolveStack(active_mesh.stack);
1389  }
1390  }
1391 
1392  painter.HighlightMesh(active_mesh, undefined, geo_object, geo_index);
1393 
1394  if (painter.ctrl.update_browser) {
1395  if (painter.ctrl.highlight && tooltip) names = [ tooltip ];
1396  painter.ActivateInBrowser(names);
1397  }
1398 
1399  if (!resolve || !resolve.obj) return tooltip;
1400 
1401  var lines = JSROOT.GEO.provideInfo(resolve.obj);
1402  lines.unshift(tooltip);
1403 
1404  return { name: resolve.obj.fName, title: resolve.obj.fTitle || resolve.obj._typename, lines: lines };
1405  }
1406 
1407  this._controls.ProcessMouseLeave = function() {
1408  this.ProcessMouseMove([]); // to disable highlight and reset browser
1409  }
1410 
1411  this._controls.ProcessDblClick = function() {
1412  if (painter._last_manifest) {
1413  painter._last_manifest.wireframe = !painter._last_manifest.wireframe;
1414  if (painter._last_hidden)
1415  painter._last_hidden.forEach(function(obj) { obj.visible = true; });
1416  delete painter._last_hidden;
1417  delete painter._last_manifest;
1418  painter.Render3D();
1419  } else {
1420  painter.adjustCameraPosition();
1421  }
1422  }
1423  }
1424 
1425  TGeoPainter.prototype.addTransformControl = function() {
1426  if (this._tcontrols) return;
1427 
1428  if ( !this.ctrl._debug && !this.ctrl._grid ) return;
1429 
1430  // FIXME: at the moment THREE.TransformControls is bogus in three.js, should be fixed and check again
1431  //return;
1432 
1433  this._tcontrols = new THREE.TransformControls( this._camera, this._renderer.domElement );
1434  this._scene.add( this._tcontrols );
1435  this._tcontrols.attach( this._toplevel );
1436  //this._tcontrols.setSize( 1.1 );
1437  var painter = this;
1438 
1439  window.addEventListener( 'keydown', function ( event ) {
1440  switch ( event.keyCode ) {
1441  case 81: // Q
1442  painter._tcontrols.setSpace( painter._tcontrols.space === "local" ? "world" : "local" );
1443  break;
1444  case 17: // Ctrl
1445  painter._tcontrols.setTranslationSnap( Math.ceil( painter._overall_size ) / 50 );
1446  painter._tcontrols.setRotationSnap( THREE.Math.degToRad( 15 ) );
1447  break;
1448  case 84: // T (Translate)
1449  painter._tcontrols.setMode( "translate" );
1450  break;
1451  case 82: // R (Rotate)
1452  painter._tcontrols.setMode( "rotate" );
1453  break;
1454  case 83: // S (Scale)
1455  painter._tcontrols.setMode( "scale" );
1456  break;
1457  case 187:
1458  case 107: // +, =, num+
1459  painter._tcontrols.setSize( painter._tcontrols.size + 0.1 );
1460  break;
1461  case 189:
1462  case 109: // -, _, num-
1463  painter._tcontrols.setSize( Math.max( painter._tcontrols.size - 0.1, 0.1 ) );
1464  break;
1465  }
1466  });
1467  window.addEventListener( 'keyup', function ( event ) {
1468  switch ( event.keyCode ) {
1469  case 17: // Ctrl
1470  painter._tcontrols.setTranslationSnap( null );
1471  painter._tcontrols.setRotationSnap( null );
1472  break;
1473  }
1474  });
1475 
1476  this._tcontrols.addEventListener( 'change', function() { painter.Render3D(0); });
1477  }
1478 
1485  TGeoPainter.prototype.nextDrawAction = function() {
1486 
1487  if (!this._clones || (this.drawing_stage == 0)) return false;
1488 
1489  if (this.drawing_stage == 1) {
1490 
1491  if (this._geom_viewer) {
1492  this._draw_all_nodes = false;
1493  this.drawing_stage = 3;
1494  return true;
1495  }
1496 
1497  // wait until worker is really started
1498  if (this.ctrl.use_worker > 0) {
1499  if (!this._worker) { this.startWorker(); return 1; }
1500  if (!this._worker_ready) return 1;
1501  }
1502 
1503  // first copy visibility flags and check how many unique visible nodes exists
1504  var numvis = this._clones.CountVisibles() || this._clones.MarkVisibles(),
1505  matrix = null, frustum = null;
1506 
1507  if (this.ctrl.select_in_view && !this._first_drawing) {
1508  // extract camera projection matrix for selection
1509 
1510  matrix = JSROOT.GEO.CreateProjectionMatrix(this._camera);
1511 
1512  frustum = JSROOT.GEO.CreateFrustum(matrix);
1513 
1514  // check if overall bounding box seen
1515  if (frustum.CheckBox(this.getGeomBoundingBox(this._toplevel))) {
1516  matrix = null; // not use camera for the moment
1517  frustum = null;
1518  }
1519  }
1520 
1521  this._current_face_limit = this.ctrl.maxlimit;
1522  if (matrix) this._current_face_limit*=1.25;
1523 
1524  // here we decide if we need worker for the drawings
1525  // main reason - too large geometry and large time to scan all camera positions
1526  var need_worker = !JSROOT.BatchMode && ((numvis > 10000) || (matrix && (this._clones.ScanVisible() > 1e5)));
1527 
1528  // worker does not work when starting from file system
1529  if (need_worker && JSROOT.source_dir.indexOf("file://")==0) {
1530  console.log('disable worker for jsroot from file system');
1531  need_worker = false;
1532  }
1533 
1534  if (need_worker && !this._worker && (this.ctrl.use_worker >= 0))
1535  this.startWorker(); // we starting worker, but it may not be ready so fast
1536 
1537  if (!need_worker || !this._worker_ready) {
1538  // var tm1 = new Date().getTime();
1539  var res = this._clones.CollectVisibles(this._current_face_limit, frustum);
1540  this._new_draw_nodes = res.lst;
1541  this._draw_all_nodes = res.complete;
1542  // var tm2 = new Date().getTime();
1543  // console.log('Collect visibles', this._new_draw_nodes.length, 'takes', tm2-tm1);
1544  this.drawing_stage = 3;
1545  return true;
1546  }
1547 
1548  var job = {
1549  collect: this._current_face_limit, // indicator for the command
1550  flags: this._clones.GetVisibleFlags(),
1551  matrix: matrix ? matrix.elements : null
1552  };
1553 
1554  this.submitToWorker(job);
1555 
1556  this.drawing_stage = 2;
1557 
1558  this.drawing_log = "Worker select visibles";
1559 
1560  return 2; // we now waiting for the worker reply
1561  }
1562 
1563  if (this.drawing_stage == 2) {
1564  // do nothing, we are waiting for worker reply
1565 
1566  this.drawing_log = "Worker select visibles";
1567 
1568  return 2;
1569  }
1570 
1571  if (this.drawing_stage == 3) {
1572  // here we merge new and old list of nodes for drawing,
1573  // normally operation is fast and can be implemented with one call
1574 
1575  this.drawing_log = "Analyse visibles";
1576 
1577  if (this._new_append_nodes) {
1578 
1579  this._new_draw_nodes = this._draw_nodes.concat(this._new_append_nodes);
1580 
1581  delete this._new_append_nodes;
1582 
1583  } else if (this._draw_nodes) {
1584 
1585  var del;
1586  if (this._geom_viewer)
1587  del = this._draw_nodes;
1588  else
1589  del = this._clones.MergeVisibles(this._new_draw_nodes, this._draw_nodes);
1590 
1591  // remove should be fast, do it here
1592  for (var n=0;n<del.length;++n)
1593  this._clones.CreateObject3D(del[n].stack, this._toplevel, 'delete_mesh');
1594 
1595  if (del.length > 0)
1596  this.drawing_log = "Delete " + del.length + " nodes";
1597  }
1598 
1599  this._draw_nodes = this._new_draw_nodes;
1600  delete this._new_draw_nodes;
1601  this.drawing_stage = 4;
1602  return true;
1603  }
1604 
1605  if (this.drawing_stage === 4) {
1606 
1607  this.drawing_log = "Collect shapes";
1608 
1609  // collect shapes
1610  var shapes = this._clones.CollectShapes(this._draw_nodes);
1611 
1612  // merge old and new list with produced shapes
1613  this._build_shapes = this._clones.MergeShapesLists(this._build_shapes, shapes);
1614 
1615  this.drawing_stage = 5;
1616  return true;
1617  }
1618 
1619 
1620  if (this.drawing_stage === 5) {
1621  // this is building of geometries,
1622  // one can ask worker to build them or do it ourself
1623 
1624  if (this.canSubmitToWorker()) {
1625  var job = { limit: this._current_face_limit, shapes: [] }, cnt = 0;
1626  for (var n=0;n<this._build_shapes.length;++n) {
1627  var clone = null, item = this._build_shapes[n];
1628  // only submit not-done items
1629  if (item.ready || item.geom) {
1630  // this is place holder for existing geometry
1631  clone = { id: item.id, ready: true, nfaces: JSROOT.GEO.numGeometryFaces(item.geom), refcnt: item.refcnt };
1632  } else {
1633  clone = JSROOT.clone(item, null, true);
1634  cnt++;
1635  }
1636 
1637  job.shapes.push(clone);
1638  }
1639 
1640  if (cnt > 0) {
1642  this.submitToWorker(job);
1643  this.drawing_log = "Worker build shapes";
1644  this.drawing_stage = 6;
1645  return 2;
1646  }
1647  }
1648 
1649  this.drawing_stage = 7;
1650  }
1651 
1652  if (this.drawing_stage === 6) {
1653  // waiting shapes from the worker, worker should activate our code
1654  return 2;
1655  }
1656 
1657  if ((this.drawing_stage === 7) || (this.drawing_stage === 8)) {
1658 
1659  if (this.drawing_stage === 7) {
1660  // building shapes
1661  var res = this._clones.BuildShapes(this._build_shapes, this._current_face_limit, 500);
1662  if (res.done) {
1663  this.ctrl.info.num_shapes = this._build_shapes.length;
1664  this.drawing_stage = 8;
1665  } else {
1666  this.ctrl.info.num_shapes = res.shapes;
1667  this.drawing_log = "Creating: " + res.shapes + " / " + this._build_shapes.length + " shapes, " + res.faces + " faces";
1668  if (res.notusedshapes < 30) return true;
1669  }
1670  }
1671 
1672  // final stage, create all meshes
1673 
1674  var tm0 = new Date().getTime(), ready = true,
1675  toplevel = this.ctrl.project ? this._full_geom : this._toplevel;
1676 
1677  for (var n = 0; n < this._draw_nodes.length; ++n) {
1678  var entry = this._draw_nodes[n];
1679  if (entry.done) continue;
1680 
1682  var shape = entry.server_shape || this._build_shapes[entry.shapeid];
1683  if (!shape.ready) {
1684  if (this.drawing_stage === 8) console.warn('shape marked as not ready when should');
1685  ready = false;
1686  continue;
1687  }
1688 
1689  entry.done = true;
1690  shape.used = true; // indicate that shape was used in building
1691 
1692  if (this.createEntryMesh(entry, shape, toplevel)) {
1693  this.ctrl.info.num_meshes++;
1694  this.ctrl.info.num_faces += shape.nfaces;
1695  }
1696 
1697  var tm1 = new Date().getTime();
1698  if (tm1 - tm0 > 500) { ready = false; break; }
1699  }
1700 
1701  if (ready) {
1702  if (this.ctrl.project) {
1703  this.drawing_log = "Build projection";
1704  this.drawing_stage = 10;
1705  return true;
1706  }
1707 
1708  this.drawing_log = "Building done";
1709  this.drawing_stage = 0;
1710  return false;
1711  }
1712 
1713  if (this.drawing_stage > 7)
1714  this.drawing_log = "Building meshes " + this.ctrl.info.num_meshes + " / " + this.ctrl.info.num_faces;
1715  return true;
1716  }
1717 
1718  if (this.drawing_stage === 9) {
1719  // wait for main painter to be ready
1720 
1721  if (!this._main_painter) {
1722  console.warn('MAIN PAINTER DISAPPER');
1723  this.drawing_stage = 0;
1724  return false;
1725  }
1726  if (!this._main_painter._drawing_ready) return 1;
1727 
1728  this.drawing_stage = 10; // just do projection
1729  }
1730 
1731  if (this.drawing_stage === 10) {
1732  this.doProjection();
1733  this.drawing_log = "Building done";
1734  this.drawing_stage = 0;
1735  return false;
1736  }
1737 
1738  console.error('never come here stage = ' + this.drawing_stage);
1739 
1740  return false;
1741  }
1742 
1745  TGeoPainter.prototype.createEntryMesh = function(entry, shape, toplevel) {
1746 
1747  if (!shape.geom || (shape.nfaces === 0)) {
1748  // node is visible, but shape does not created
1749  this._clones.CreateObject3D(entry.stack, toplevel, 'delete_mesh');
1750  return false;
1751  }
1752 
1753  // workaround for the TGeoOverlap, where two branches should get predefined color
1754  if (this._splitColors && entry.stack) {
1755  if (entry.stack[0]===0) entry.custom_color = "green"; else
1756  if (entry.stack[0]===1) entry.custom_color = "blue";
1757  }
1758 
1759  var prop = this._clones.getDrawEntryProperties(entry);
1760 
1761  var obj3d = this._clones.CreateObject3D(entry.stack, toplevel, this.ctrl);
1762 
1763  prop.material.wireframe = this.ctrl.wireframe;
1764 
1765  prop.material.side = this.ctrl.bothSides ? THREE.DoubleSide : THREE.FrontSide;
1766 
1767  var mesh;
1768 
1769  if (obj3d.matrixWorld.determinant() > -0.9) {
1770  mesh = new THREE.Mesh( shape.geom, prop.material );
1771  } else {
1772  mesh = JSROOT.GEO.createFlippedMesh(obj3d, shape, prop.material);
1773  }
1774 
1775  obj3d.add(mesh);
1776 
1777  // keep full stack of nodes
1778  mesh.stack = entry.stack;
1779  mesh.renderOrder = this._clones.maxdepth - entry.stack.length; // order of transparency handling
1780 
1781  // keep hierarchy level
1782  mesh.$jsroot_order = obj3d.$jsroot_depth;
1783 
1784  // set initial render order, when camera moves, one must refine it
1785  //mesh.$jsroot_order = mesh.renderOrder =
1786  // this._clones.maxdepth - ((obj3d.$jsroot_depth !== undefined) ? obj3d.$jsroot_depth : entry.stack.length);
1787 
1788  if (this.ctrl._debug || this.ctrl._full) {
1789  var wfg = new THREE.WireframeGeometry( mesh.geometry ),
1790  wfm = new THREE.LineBasicMaterial( { color: prop.fillcolor, linewidth: prop.linewidth || 1 } ),
1791  helper = new THREE.LineSegments(wfg, wfm);
1792  obj3d.add(helper);
1793  }
1794 
1795  if (this.ctrl._bound || this.ctrl._full) {
1796  var boxHelper = new THREE.BoxHelper( mesh );
1797  obj3d.add( boxHelper );
1798  }
1799 
1800  return true;
1801  }
1802 
1807  TGeoPainter.prototype.appendMoreNodes = function(nodes, from_drawing) {
1808  if (this.drawing_stage && !from_drawing) {
1809  this._provided_more_nodes = nodes;
1810  return;
1811  }
1812 
1813  // delete old nodes
1814  if (this._more_nodes)
1815  for (var n=0;n<this._more_nodes.length;++n) {
1816  var entry = this._more_nodes[n];
1817  var obj3d = this._clones.CreateObject3D(entry.stack, this._toplevel, 'delete_mesh');
1818  JSROOT.Painter.DisposeThreejsObject(obj3d);
1819  JSROOT.GEO.cleanupShape(entry.server_shape);
1820  delete entry.server_shape;
1821  }
1822 
1823  delete this._more_nodes;
1824 
1825  if (!nodes) return;
1826 
1827  var real_nodes = [];
1828 
1829  for (var k=0;k<nodes.length;++k) {
1830  var entry = nodes[k];
1831  var shape = entry.server_shape;
1832  if (!shape || !shape.ready) continue;
1833 
1834  entry.done = true;
1835  shape.used = true; // indicate that shape was used in building
1836 
1837  if (this.createEntryMesh(entry, shape, this._toplevel))
1838  real_nodes.push(entry);
1839  }
1840 
1841  // remember additional nodes only if they include shape - otherwise one can ignore them
1842  if (real_nodes) this._more_nodes = real_nodes;
1843 
1844  if (!from_drawing) this.Render3D();
1845  }
1846 
1851  TGeoPainter.prototype.getProjectionSource = function() {
1852  if (this._clones_owner)
1853  return this._full_geom;
1854  if (!this._main_painter) {
1855  console.warn('MAIN PAINTER DISAPPER');
1856  return null;
1857  }
1858  if (!this._main_painter._drawing_ready) {
1859  console.warn('MAIN PAINTER NOT READY WHEN DO PROJECTION');
1860  return null;
1861  }
1862  return this._main_painter._toplevel;
1863  }
1864 
1865  TGeoPainter.prototype.getGeomBoundingBox = function(topitem, scalar) {
1866  var box3 = new THREE.Box3(), check_any = !this._clones;
1867 
1868  if (!topitem) {
1869  box3.min.x = box3.min.y = box3.min.z = -1;
1870  box3.max.x = box3.max.y = box3.max.z = 1;
1871  return box3;
1872  }
1873 
1874  box3.makeEmpty();
1875 
1876  topitem.traverse(function(mesh) {
1877  if (check_any || (mesh.stack && (mesh instanceof THREE.Mesh)) ||
1878  (mesh.main_track && (mesh instanceof THREE.LineSegments)))
1879  JSROOT.GEO.getBoundingBox(mesh, box3);
1880  });
1881 
1882  if (scalar !== undefined) box3.expandByVector(box3.getSize(new THREE.Vector3()).multiplyScalar(scalar));
1883 
1884  return box3;
1885  }
1886 
1887 
1888  TGeoPainter.prototype.doProjection = function() {
1889  var toplevel = this.getProjectionSource(), pthis = this;
1890 
1891  if (!toplevel) return false;
1892 
1893  JSROOT.Painter.DisposeThreejsObject(this._toplevel, true);
1894 
1895  var axis = this.ctrl.project;
1896 
1897  if (this.ctrl.projectPos === undefined) {
1898 
1899  var bound = this.getGeomBoundingBox(toplevel),
1900  min = bound.min[this.ctrl.project], max = bound.max[this.ctrl.project],
1901  mean = (min+max)/2;
1902 
1903  if ((min<0) && (max>0) && (Math.abs(mean) < 0.2*Math.max(-min,max))) mean = 0; // if middle is around 0, use 0
1904 
1905  this.ctrl.projectPos = mean;
1906  }
1907 
1908  toplevel.traverse(function(mesh) {
1909  if (!(mesh instanceof THREE.Mesh) || !mesh.stack) return;
1910 
1911  var geom2 = JSROOT.GEO.projectGeometry(mesh.geometry, mesh.parent.matrixWorld, pthis.ctrl.project, pthis.ctrl.projectPos, mesh._flippedMesh);
1912 
1913  if (!geom2) return;
1914 
1915  var mesh2 = new THREE.Mesh( geom2, mesh.material.clone() );
1916 
1917  pthis._toplevel.add(mesh2);
1918 
1919  mesh2.stack = mesh.stack;
1920  });
1921 
1922  return true;
1923  }
1924 
1925  TGeoPainter.prototype.SameMaterial = function(node1, node2) {
1926 
1927  if ((node1===null) || (node2===null)) return node1 === node2;
1928 
1929  if (node1.fVolume.fLineColor >= 0)
1930  return (node1.fVolume.fLineColor === node2.fVolume.fLineColor);
1931 
1932  var m1 = (node1.fVolume.fMedium !== null) ? node1.fVolume.fMedium.fMaterial : null;
1933  var m2 = (node2.fVolume.fMedium !== null) ? node2.fVolume.fMedium.fMaterial : null;
1934 
1935  if (m1 === m2) return true;
1936 
1937  if ((m1 === null) || (m2 === null)) return false;
1938 
1939  return (m1.fFillStyle === m2.fFillStyle) && (m1.fFillColor === m2.fFillColor);
1940  }
1941 
1942  TGeoPainter.prototype.changedLight = function(box) {
1943  if (!this._camera) return;
1944 
1945  var need_render = !box;
1946 
1947  if (!box) box = this.getGeomBoundingBox(this._toplevel);
1948 
1949  var sizex = box.max.x - box.min.x,
1950  sizey = box.max.y - box.min.y,
1951  sizez = box.max.z - box.min.z,
1952  lights = [];
1953 
1954  for (var k=0;k<this._camera.children.length;++k) {
1955  var light = this._camera.children[k], enabled = false;
1956  if (!light.isPointLight) continue;
1957  switch (k) {
1958  case 0: light.position.set(sizex/5, sizey/5, sizez/5); enabled = this.ctrl.light.specular; break;
1959  case 1: light.position.set(0, 0, sizez/2); enabled = this.ctrl.light.front; break;
1960  case 2: light.position.set(0, 2*sizey, 0); enabled = this.ctrl.light.top; break;
1961  case 3: light.position.set(0, -2*sizey, 0); enabled = this.ctrl.light.bottom; break;
1962  case 4: light.position.set(-2*sizex, 0, 0); enabled = this.ctrl.light.left; break;
1963  case 5: light.position.set(2*sizex, 0, 0); enabled = this.ctrl.light.right; break;
1964  }
1965  light.power = enabled ? Math.PI*4 : 0;
1966  if (enabled) lights.push(light);
1967  }
1968 
1969  // keep light power of all soources constant
1970  lights.forEach(function(light) { light.power = 4*Math.PI/lights.length; })
1971 
1972  if (need_render) this.Render3D();
1973  }
1974 
1975  TGeoPainter.prototype.createScene = function(w, h) {
1976  // three.js 3D drawing
1977  this._scene = new THREE.Scene();
1978  this._scene.fog = new THREE.Fog(0xffffff, 1, 10000);
1979  this._scene.overrideMaterial = new THREE.MeshLambertMaterial( { color: 0x7000ff, transparent: true, opacity: 0.2, depthTest: false } );
1980 
1981  this._scene_width = w;
1982  this._scene_height = h;
1983 
1984  if (this.ctrl.ortho_camera) {
1985  this._camera = new THREE.OrthographicCamera(-600, 600, -600, 600, 1, 10000);
1986  } else {
1987  this._camera = new THREE.PerspectiveCamera(25, w / h, 1, 10000);
1988  this._camera.up = this.ctrl._yup ? new THREE.Vector3(0,1,0) : new THREE.Vector3(0,0,1);
1989  }
1990 
1991  this._scene.add( this._camera );
1992 
1993  this._selected_mesh = null;
1994 
1995  this._overall_size = 10;
1996 
1997  this._toplevel = new THREE.Object3D();
1998 
1999  this._scene.add(this._toplevel);
2000 
2001  var rrr = JSROOT.Painter.Create3DRenderer(w, h, this._usesvg, this._usesvgimg, this._webgl,
2002  { antialias: true, logarithmicDepthBuffer: false, preserveDrawingBuffer: true });
2003 
2004  this._webgl = rrr.usewebgl;
2005  this._renderer = rrr.renderer;
2006 
2007  if (this._renderer.setPixelRatio && !JSROOT.nodejs)
2008  this._renderer.setPixelRatio(window.devicePixelRatio);
2009  this._renderer.setSize(w, h, !this._fit_main_area);
2010  this._renderer.localClippingEnabled = true;
2011 
2012  this._renderer.setClearColor(this.ctrl.background, 1);
2013 
2014 /* if (usesvg) {
2015  // this._renderer = new THREE.SVGRenderer( { precision: 0, astext: true } );
2016  this._renderer = THREE.CreateSVGRenderer(false, 0, document);
2017  if (this._renderer.makeOuterHTML !== undefined) {
2018  // this is indication of new three.js functionality
2019  if (!JSROOT.svg_workaround) JSROOT.svg_workaround = [];
2020  this._renderer.workaround_id = JSROOT.svg_workaround.length;
2021  JSROOT.svg_workaround[this._renderer.workaround_id] = "<svg></svg>"; // dummy, need to be replaced
2022 
2023  this._renderer.domElement = document.createElementNS( 'http://www.w3.org/2000/svg', 'path');
2024  this._renderer.domElement.setAttribute('jsroot_svg_workaround', this._renderer.workaround_id);
2025  }
2026  } else {
2027  this._renderer = webgl ?
2028  new THREE.WebGLRenderer({ antialias: true, logarithmicDepthBuffer: false,
2029  preserveDrawingBuffer: true }) :
2030  new THREE.SoftwareRenderer({ antialias: true });
2031  this._renderer.setPixelRatio(window.devicePixelRatio);
2032  }
2033  this._renderer.setSize(w, h, !this._fit_main_area);
2034  this._renderer.localClippingEnabled = true;
2035 
2036  */
2037 
2038  if (this._fit_main_area && !this._usesvg) {
2039  this._renderer.domElement.style.width = "100%";
2040  this._renderer.domElement.style.height = "100%";
2041  var main = this.select_main();
2042  if (main.style('position')=='static') main.style('position','relative');
2043  }
2044 
2045  this._animating = false;
2046 
2047  // Clipping Planes
2048 
2049  this.ctrl.bothSides = false; // which material kind should be used
2050  this._clipPlanes = [ new THREE.Plane(new THREE.Vector3(1, 0, 0), 0),
2051  new THREE.Plane(new THREE.Vector3(0, this.ctrl._yup ? -1 : 1, 0), 0),
2052  new THREE.Plane(new THREE.Vector3(0, 0, this.ctrl._yup ? 1 : -1), 0) ];
2053 
2054 
2055  // Lights - add 6 sources, place them once dimension of geometry is known
2056  for (var n=0;n<6;++n) {
2057  var light = new THREE.PointLight(0xefefef, n==0 ? 1 : 0);
2058  if (n==0) light.position.set(10, 10, 10);
2059  this._camera.add( light );
2060  }
2061 
2062  // Smooth Lighting Shader (Screen Space Ambient Occlusion)
2063  // http://threejs.org/examples/webgl_postprocessing_ssao.html
2064 
2065  if (this._webgl && (this.ctrl.ssao.enabled || this.ctrl.outline)) {
2066 
2067  if (this.ctrl.outline && (typeof this.createOutline == "function")) {
2068  this._effectComposer = new THREE.EffectComposer( this._renderer );
2069  this._effectComposer.addPass( new THREE.RenderPass( this._scene, this._camera ) );
2070  this.createOutline(w, h);
2071  } else if (this.ctrl.ssao.enabled) {
2072  this.createSSAO();
2073  }
2074  }
2075 
2076  if (this._fit_main_area && (this._usesvg || this._usesvgimg)) {
2077  // create top-most SVG for geomtery drawings
2078  var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
2079  svg.appendChild(rrr.dom);
2080  return svg;
2081  }
2082 
2083  return rrr.dom;
2084  }
2085 
2086 
2087  TGeoPainter.prototype.startDrawGeometry = function(force) {
2088 
2089  if (!force && (this.drawing_stage!==0)) {
2090  this._draw_nodes_again = true;
2091  return;
2092  }
2093 
2094  if (this._clones_owner && this._clones)
2095  this._clones.SetDefaultColors(this.ctrl.dflt_colors);
2096 
2097  this._startm = new Date().getTime();
2098  this._last_render_tm = this._startm;
2099  this._last_render_meshes = 0;
2100  this.drawing_stage = 1;
2101  this._drawing_ready = false;
2102  this.drawing_log = "collect visible";
2103  this.ctrl.info.num_meshes = 0;
2104  this.ctrl.info.num_faces = 0;
2105  this.ctrl.info.num_shapes = 0;
2106  this._selected_mesh = null;
2107 
2108  if (this.ctrl.project) {
2109  if (this._clones_owner) {
2110  if (this._full_geom) {
2111  this.drawing_stage = 10;
2112  this.drawing_log = "build projection";
2113  } else {
2114  this._full_geom = new THREE.Object3D();
2115  }
2116 
2117  } else {
2118  this.drawing_stage = 9;
2119  this.drawing_log = "wait for main painter";
2120  }
2121  }
2122 
2123  delete this._last_manifest;
2124  delete this._last_hidden; // clear list of hidden objects
2125 
2126  delete this._draw_nodes_again; // forget about such flag
2127 
2128  this.continueDraw();
2129  }
2130 
2131  TGeoPainter.prototype.resetAdvanced = function() {
2132  this.ctrl.ssao.kernelRadius = 16;
2133  this.ctrl.ssao.output = THREE.SSAOPass.OUTPUT.Default;
2134 
2135  this.ctrl.depthTest = true;
2136  this.ctrl.clipIntersect = true;
2137  this.ctrl.depthMethod = "ray";
2138 
2139  this.changedDepthMethod("norender");
2140  this.changedDepthTest();
2141  }
2142 
2143  TGeoPainter.prototype.getGeomBox = function() {
2144  var extras = this.getExtrasContainer('collect');
2145 
2146  var box = this.getGeomBoundingBox(this._toplevel);
2147 
2148  if (extras)
2149  for (var k=0;k<extras.length;++k) this._toplevel.add(extras[k]);
2150 
2151  return box;
2152  }
2153 
2154  TGeoPainter.prototype.getOverallSize = function(force) {
2155  if (!this._overall_size || force) {
2156  var box = this.getGeomBoundingBox(this._toplevel);
2157 
2158  // if detect of coordinates fails - ignore
2159  if (isNaN(box.min.x)) return 1000;
2160 
2161  var sizex = box.max.x - box.min.x,
2162  sizey = box.max.y - box.min.y,
2163  sizez = box.max.z - box.min.z;
2164 
2165  this._overall_size = 2 * Math.max(sizex, sizey, sizez);
2166  }
2167 
2168  return this._overall_size;
2169  }
2170 
2172  TGeoPainter.prototype.createSnapshot = function(filename) {
2173  if (!this._renderer) return;
2174  this.Render3D(0);
2175  var dataUrl = this._renderer.domElement.toDataURL("image/png");
2176  if (filename==="asis") return dataUrl;
2177  dataUrl.replace("image/png", "image/octet-stream");
2178  var link = document.createElement('a');
2179  if (typeof link.download === 'string') {
2180  document.body.appendChild(link); //Firefox requires the link to be in the body
2181  link.download = filename || "geometry.png";
2182  link.href = dataUrl;
2183  link.click();
2184  document.body.removeChild(link); //remove the link when done
2185  }
2186  }
2187 
2191  TGeoPainter.prototype.produceCameraUrl = function(prec) {
2192 
2193  if (!this._lookat || !this._camera0pos || !this._camera || !this.ctrl) return;
2194 
2195  var pos1 = new THREE.Vector3().add(this._camera0pos).sub(this._lookat),
2196  pos2 = new THREE.Vector3().add(this._camera.position).sub(this._lookat),
2197  len1 = pos1.length(), len2 = pos2.length(),
2198  zoom = this.ctrl.zoom * len2 / len1 * 100;
2199 
2200  if (zoom < 1) zoom = 1; else if (zoom>10000) zoom = 10000;
2201 
2202  pos1.normalize();
2203  pos2.normalize();
2204 
2205  var quat = new THREE.Quaternion();
2206  quat.setFromUnitVectors(pos1, pos2);
2207 
2208  var euler = new THREE.Euler();
2209  euler.setFromQuaternion(quat, "YZX");
2210 
2211  var roty = euler.y / Math.PI * 180,
2212  rotz = euler.z / Math.PI * 180;
2213 
2214  if (roty<0) roty += 360;
2215  if (rotz<0) rotz += 360;
2216 
2217  return "roty" + roty.toFixed(prec || 0) + ",rotz" + rotz.toFixed(prec || 0) + ",zoom" + zoom.toFixed(prec || 0);
2218  }
2219 
2221  TGeoPainter.prototype.calculateZoom = function() {
2222  if (this._camera0pos && this._camera && this._lookat) {
2223  var pos1 = new THREE.Vector3().add(this._camera0pos).sub(this._lookat),
2224  pos2 = new THREE.Vector3().add(this._camera.position).sub(this._lookat);
2225  return pos2.length() / pos1.length();
2226  }
2227 
2228  return 0;
2229  }
2230 
2231 
2233  TGeoPainter.prototype.adjustCameraPosition = function(first_time, keep_zoom) {
2234  if (!this._toplevel) return;
2235 
2236  var box = this.getGeomBoundingBox(this._toplevel);
2237 
2238  // if detect of coordinates fails - ignore
2239  if (isNaN(box.min.x)) return;
2240 
2241  var sizex = box.max.x - box.min.x,
2242  sizey = box.max.y - box.min.y,
2243  sizez = box.max.z - box.min.z,
2244  midx = (box.max.x + box.min.x)/2,
2245  midy = (box.max.y + box.min.y)/2,
2246  midz = (box.max.z + box.min.z)/2;
2247 
2248  this._overall_size = 2 * Math.max(sizex, sizey, sizez);
2249 
2250  this._camera.near = this._overall_size / 350;
2251  this._camera.far = this._overall_size * 12;
2252  this._scene.fog.near = this._overall_size * 2;
2253  this._scene.fog.far = this._overall_size * 12;
2254 
2255  if (first_time)
2256  for (var naxis=0;naxis<3;++naxis) {
2257  var cc = this.ctrl.clip[naxis];
2258  cc.min = box.min[cc.name];
2259  cc.max = box.max[cc.name];
2260  var sz = cc.max - cc.min;
2261  cc.max += sz*0.01;
2262  cc.min -= sz*0.01;
2263  if (!cc.value)
2264  cc.value = (cc.min + cc.max) / 2;
2265  else if (cc.value < cc.min)
2266  cc.value = cc.min;
2267  else if (cc.value > cc.max)
2268  cc.value = cc.max;
2269  }
2270 
2271  if (this.ctrl.ortho_camera) {
2272  this._camera.left = box.min.x;
2273  this._camera.right = box.max.x;
2274  this._camera.top = box.max.y;
2275  this._camera.bottom = box.min.y;
2276  }
2277 
2278  // this._camera.far = 100000000000;
2279 
2280  this._camera.updateProjectionMatrix();
2281 
2282  var k = 2*this.ctrl.zoom,
2283  max_all = Math.max(sizex,sizey,sizez);
2284 
2285  if ((this.ctrl.rotatey || this.ctrl.rotatez) && this.ctrl.can_rotate) {
2286 
2287  var prev_zoom = this.calculateZoom();
2288  if (keep_zoom && prev_zoom) k = 2*prev_zoom;
2289 
2290  var euler = new THREE.Euler( 0, this.ctrl.rotatey/180.*Math.PI, this.ctrl.rotatez/180.*Math.PI, 'YZX' );
2291 
2292  this._camera.position.set(-k*max_all, 0, 0);
2293  this._camera.position.applyEuler(euler);
2294  this._camera.position.add(new THREE.Vector3(midx,midy,midz));
2295 
2296  if (keep_zoom && prev_zoom) {
2297  var actual_zoom = this.calculateZoom();
2298  k *= prev_zoom/actual_zoom;
2299 
2300  this._camera.position.set(-k*max_all, 0, 0);
2301  this._camera.position.applyEuler(euler);
2302  this._camera.position.add(new THREE.Vector3(midx,midy,midz));
2303  }
2304 
2305  } else if (this.ctrl.ortho_camera) {
2306  this._camera.position.set(midx, midy, Math.max(sizex,sizey));
2307  } else if (this.ctrl.project) {
2308  switch (this.ctrl.project) {
2309  case 'x': this._camera.position.set(k*1.5*Math.max(sizey,sizez), 0, 0); break;
2310  case 'y': this._camera.position.set(0, k*1.5*Math.max(sizex,sizez), 0); break;
2311  case 'z': this._camera.position.set(0, 0, k*1.5*Math.max(sizex,sizey)); break;
2312  }
2313  } else if (this.ctrl._yup) {
2314  this._camera.position.set(midx-k*Math.max(sizex,sizez), midy+k*sizey, midz-k*Math.max(sizex,sizez));
2315  } else {
2316  this._camera.position.set(midx-k*Math.max(sizex,sizey), midy-k*Math.max(sizex,sizey), midz+k*sizez);
2317  }
2318 
2319  this._lookat = new THREE.Vector3(midx, midy, midz);
2320  this._camera0pos = new THREE.Vector3(-2*max_all, 0, 0); // virtual 0 position, where rotation starts
2321  this._camera.lookAt(this._lookat);
2322 
2323  this.changedLight(box);
2324 
2325  if (this._controls) {
2326  this._controls.target.copy(this._lookat);
2327  this._controls.update();
2328  }
2329 
2330  // recheck which elements to draw
2331  if (this.ctrl.select_in_view)
2332  this.startDrawGeometry();
2333  }
2334 
2335  TGeoPainter.prototype.setCameraPosition = function(rotatey, rotatez, zoom) {
2336  if (!this.ctrl) return;
2337  this.ctrl.rotatey = rotatey || 0;
2338  this.ctrl.rotatez = rotatez || 0;
2339  var preserve_zoom = false;
2340  if (zoom && !isNaN(zoom)) {
2341  this.ctrl.zoom = zoom;
2342  } else {
2343  preserve_zoom = true;
2344  }
2345  this.adjustCameraPosition(false, preserve_zoom);
2346  }
2347 
2348  TGeoPainter.prototype.focusOnItem = function(itemname) {
2349 
2350  if (!itemname || !this._clones) return;
2351 
2352  var stack = this._clones.FindStackByName(itemname);
2353 
2354  if (!stack) return;
2355 
2356  var info = this._clones.ResolveStack(stack, true);
2357 
2358  this.focusCamera( info, false );
2359  }
2360 
2361  TGeoPainter.prototype.focusCamera = function( focus, autoClip ) {
2362 
2363  if (this.ctrl.project)
2364  return this.adjustCameraPosition();
2365 
2366  var box = new THREE.Box3();
2367  if (focus === undefined) {
2368  box = this.getGeomBoundingBox(this._toplevel);
2369  } else if (focus instanceof THREE.Mesh) {
2370  box.setFromObject(focus);
2371  } else {
2372  var center = new THREE.Vector3().setFromMatrixPosition(focus.matrix),
2373  node = focus.node,
2374  halfDelta = new THREE.Vector3( node.fDX, node.fDY, node.fDZ ).multiplyScalar(0.5);
2375  box.min = center.clone().sub(halfDelta);
2376  box.max = center.clone().add(halfDelta);
2377  }
2378 
2379  var sizex = box.max.x - box.min.x,
2380  sizey = box.max.y - box.min.y,
2381  sizez = box.max.z - box.min.z,
2382  midx = (box.max.x + box.min.x)/2,
2383  midy = (box.max.y + box.min.y)/2,
2384  midz = (box.max.z + box.min.z)/2;
2385 
2386  var position;
2387  if (this.ctrl._yup)
2388  position = new THREE.Vector3(midx-2*Math.max(sizex,sizez), midy+2*sizey, midz-2*Math.max(sizex,sizez));
2389  else
2390  position = new THREE.Vector3(midx-2*Math.max(sizex,sizey), midy-2*Math.max(sizex,sizey), midz+2*sizez);
2391 
2392  var target = new THREE.Vector3(midx, midy, midz);
2393 
2394  // Find to points to animate "lookAt" between
2395  var dist = this._camera.position.distanceTo(target);
2396  var oldTarget = this._controls.target;
2397 
2398  // probably, reduce number of frames
2399  var frames = 50, step = 0;
2400 
2401  // Amount to change camera position at each step
2402  var posIncrement = position.sub(this._camera.position).divideScalar(frames);
2403  // Amount to change "lookAt" so it will end pointed at target
2404  var targetIncrement = target.sub(oldTarget).divideScalar(frames);
2405  // console.log( targetIncrement );
2406 
2407  autoClip = autoClip && this._webgl;
2408 
2409  // Automatic Clipping
2410  if (autoClip) {
2411  for (var axis = 0; axis<3; ++axis) {
2412  var cc = this.ctrl.clip[axis];
2413  if (!cc.enabled) { cc.value = cc.min; cc.enabled = true; }
2414  cc.inc = ((cc.min + cc.max) / 2 - cc.value) / frames;
2415  }
2416  this.updateClipping();
2417  }
2418 
2419  var painter = this;
2420  this._animating = true;
2421 
2422  // Interpolate //
2423 
2424  function animate() {
2425  if (painter._animating === undefined) return;
2426 
2427  if (painter._animating) {
2428  requestAnimationFrame( animate );
2429  } else {
2430  if (!painter._geom_viewer)
2431  painter.startDrawGeometry();
2432  }
2433  var smoothFactor = -Math.cos( ( 2.0 * Math.PI * step ) / frames ) + 1.0;
2434  painter._camera.position.add( posIncrement.clone().multiplyScalar( smoothFactor ) );
2435  oldTarget.add( targetIncrement.clone().multiplyScalar( smoothFactor ) );
2436  painter._lookat = oldTarget;
2437  painter._camera.lookAt( painter._lookat );
2438  painter._camera.updateProjectionMatrix();
2439 
2440  var tm1 = new Date().getTime();
2441  if (autoClip) {
2442  for (var axis = 0; axis<3; ++axis)
2443  painter.ctrl.clip[axis].value += painter.ctrl.clip[axis].inc * smoothFactor;
2444  painter.updateClipping();
2445  } else {
2446  painter.Render3D(0);
2447  }
2448  var tm2 = new Date().getTime();
2449  if ((step==0) && (tm2-tm1 > 200)) frames = 20;
2450  step++;
2451  painter._animating = step < frames;
2452  }
2453 
2454  animate();
2455 
2456  // this._controls.update();
2457  }
2458 
2459  TGeoPainter.prototype.autorotate = function(speed) {
2460 
2461  var rotSpeed = (speed === undefined) ? 2.0 : speed,
2462  painter = this, last = new Date();
2463 
2464  function animate() {
2465  if (!painter._renderer || !painter.ctrl) return;
2466 
2467  var current = new Date();
2468 
2469  if ( painter.ctrl.rotate ) requestAnimationFrame( animate );
2470 
2471  if (painter._controls) {
2472  painter._controls.autoRotate = painter.ctrl.rotate;
2473  painter._controls.autoRotateSpeed = rotSpeed * ( current.getTime() - last.getTime() ) / 16.6666;
2474  painter._controls.update();
2475  }
2476  last = new Date();
2477  painter.Render3D(0);
2478  }
2479 
2480  if (this._webgl) animate();
2481  }
2482 
2483  TGeoPainter.prototype.completeScene = function() {
2484 
2485  if ( this.ctrl._debug || this.ctrl._grid ) {
2486  if ( this.ctrl._full ) {
2487  var boxHelper = new THREE.BoxHelper(this._toplevel);
2488  this._scene.add( boxHelper );
2489  }
2490  this._scene.add( new THREE.AxesHelper( 2 * this._overall_size ) );
2491  this._scene.add( new THREE.GridHelper( Math.ceil( this._overall_size), Math.ceil( this._overall_size ) / 50 ) );
2492  this.helpText("<font face='verdana' size='1' color='red'><center>Transform Controls<br>" +
2493  "'T' translate | 'R' rotate | 'S' scale<br>" +
2494  "'+' increase size | '-' decrease size<br>" +
2495  "'W' toggle wireframe/solid display<br>"+
2496  "keep 'Ctrl' down to snap to grid</center></font>");
2497  }
2498  }
2499 
2502  TGeoPainter.prototype.drawCount = function(unqievis, clonetm) {
2503 
2504  var res = 'Unique nodes: ' + this._clones.nodes.length + '<br/>' +
2505  'Unique visible: ' + unqievis + '<br/>' +
2506  'Time to clone: ' + clonetm + 'ms <br/>';
2507 
2508  // need to fill cached value line numvischld
2509  this._clones.ScanVisible();
2510 
2511  var painter = this, nshapes = 0;
2512 
2513  var arg = {
2514  cnt: [],
2515  func: function(node) {
2516  if (this.cnt[this.last]===undefined)
2517  this.cnt[this.last] = 1;
2518  else
2519  this.cnt[this.last]++;
2520 
2521  nshapes += JSROOT.GEO.CountNumShapes(painter._clones.GetNodeShape(node.id));
2522 
2523  // for debugginf - search if there some TGeoHalfSpace
2524  //if (JSROOT.GEO.HalfSpace) {
2525  // var entry = this.CopyStack();
2526  // var res = painter._clones.ResolveStack(entry.stack);
2527  // console.log('SAW HALF SPACE', res.name);
2528  // JSROOT.GEO.HalfSpace = false;
2529  //}
2530  return true;
2531  }
2532  };
2533 
2534  var tm1 = new Date().getTime();
2535  var numvis = this._clones.ScanVisible(arg);
2536  var tm2 = new Date().getTime();
2537 
2538  res += 'Total visible nodes: ' + numvis + '<br/>';
2539  res += 'Total shapes: ' + nshapes + '<br/>';
2540 
2541  for (var lvl=0;lvl<arg.cnt.length;++lvl) {
2542  if (arg.cnt[lvl] !== undefined)
2543  res += (' lvl' + lvl + ': ' + arg.cnt[lvl] + '<br/>');
2544  }
2545 
2546  res += "Time to scan: " + (tm2-tm1) + "ms <br/>";
2547 
2548  res += "<br/><br/>Check timing for matrix calculations ...<br/>";
2549 
2550  var elem = this.select_main().style('overflow', 'auto').html(res);
2551 
2552  setTimeout(function() {
2553  arg.domatrix = true;
2554  tm1 = new Date().getTime();
2555  numvis = painter._clones.ScanVisible(arg);
2556  tm2 = new Date().getTime();
2557  elem.append("p").text("Time to scan with matrix: " + (tm2-tm1) + "ms");
2558  painter.DrawingReady();
2559  }, 100);
2560 
2561  return this;
2562  }
2563 
2564 
2565  TGeoPainter.prototype.PerformDrop = function(obj, itemname, hitem, opt, call_back) {
2566 
2567  if (obj && (obj.$kind==='TTree')) {
2568  // drop tree means function call which must extract tracks from provided tree
2569 
2570  var funcname = "extract_geo_tracks";
2571 
2572  if (opt && opt.indexOf("$")>0) {
2573  funcname = opt.substr(0, opt.indexOf("$"));
2574  opt = opt.substr(opt.indexOf("$")+1);
2575  }
2576 
2577  var func = JSROOT.findFunction(funcname);
2578 
2579  if (!func) return JSROOT.CallBack(call_back);
2580 
2581  var geo_painter = this;
2582 
2583  return func(obj, opt, function(tracks) {
2584  if (tracks) {
2585  geo_painter.drawExtras(tracks, "", false); // FIXME: probably tracks should be remembered??
2586  this.updateClipping(true);
2587  geo_painter.Render3D(100);
2588  }
2589  JSROOT.CallBack(call_back); // finally callback
2590  });
2591  }
2592 
2593  if (this.drawExtras(obj, itemname)) {
2594  if (hitem) hitem._painter = this; // set for the browser item back pointer
2595  }
2596 
2597  JSROOT.CallBack(call_back);
2598  }
2599 
2600  TGeoPainter.prototype.MouseOverHierarchy = function(on, itemname, hitem) {
2601  // function called when mouse is going over the item in the browser
2602 
2603  if (!this.ctrl) return; // protection for cleaned-up painter
2604 
2605  var obj = hitem._obj;
2606  if (this.ctrl._debug)
2607  console.log('Mouse over', on, itemname, (obj ? obj._typename : "---"));
2608 
2609  // let's highlight tracks and hits only for the time being
2610  if (!obj || (obj._typename !== "TEveTrack" && obj._typename !== "TEvePointSet" && obj._typename !== "TPolyMarker3D")) return;
2611 
2612  this.HighlightMesh(null, 0x00ff00, on ? obj : null);
2613  }
2614 
2615  TGeoPainter.prototype.clearExtras = function() {
2616  this.getExtrasContainer("delete");
2617  delete this._extraObjects; // workaround, later will be normal function
2618  this.Render3D();
2619  }
2620 
2624  TGeoPainter.prototype.addExtra = function(obj, itemname) {
2625  if (this._extraObjects === undefined)
2626  this._extraObjects = JSROOT.Create("TList");
2627 
2628  if (this._extraObjects.arr.indexOf(obj)>=0) return false;
2629 
2630  this._extraObjects.Add(obj, itemname);
2631 
2632  delete obj.$hidden_via_menu; // remove previous hidden property
2633 
2634  return true;
2635  }
2636 
2637  TGeoPainter.prototype.ExtraObjectVisible = function(hpainter, hitem, toggle) {
2638  if (!this._extraObjects) return;
2639 
2640  var itemname = hpainter.itemFullName(hitem),
2641  indx = this._extraObjects.opt.indexOf(itemname);
2642 
2643  if ((indx<0) && hitem._obj) {
2644  indx = this._extraObjects.arr.indexOf(hitem._obj);
2645  // workaround - if object found, replace its name
2646  if (indx>=0) this._extraObjects.opt[indx] = itemname;
2647  }
2648 
2649  if (indx < 0) return;
2650 
2651  var obj = this._extraObjects.arr[indx],
2652  res = obj.$hidden_via_menu ? false : true;
2653 
2654  if (toggle) {
2655  obj.$hidden_via_menu = res; res = !res;
2656 
2657  var mesh = null;
2658  // either found painted object or just draw once again
2659  this._toplevel.traverse(function(node) { if (node.geo_object === obj) mesh = node; });
2660 
2661  if (mesh) mesh.visible = res; else
2662  if (res) {
2663  this.drawExtras(obj, "", false);
2664  this.updateClipping(true);
2665  }
2666 
2667  if (mesh || res) this.Render3D();
2668  }
2669 
2670  return res;
2671  }
2672 
2673  TGeoPainter.prototype.drawExtras = function(obj, itemname, add_objects) {
2674  if (!obj || !obj._typename) return false;
2675 
2676  // if object was hidden via menu, do not redraw it with next draw call
2677  if (!add_objects && obj.$hidden_via_menu) return false;
2678 
2679  var isany = false, do_render = false;
2680  if (add_objects === undefined) {
2681  add_objects = true;
2682  do_render = true;
2683  }
2684 
2685  if ((obj._typename === "TList") || (obj._typename === "TObjArray")) {
2686  if (!obj.arr) return false;
2687  for (var n=0;n<obj.arr.length;++n) {
2688  var sobj = obj.arr[n], sname = obj.opt ? obj.opt[n] : "";
2689  if (!sname) sname = (itemname || "<prnt>") + "/[" + n + "]";
2690  if (this.drawExtras(sobj, sname, add_objects)) isany = true;
2691  }
2692  } else if (obj._typename === 'THREE.Mesh') {
2693  // adding mesh as is
2694  this.getExtrasContainer().add(obj);
2695  isany = true;
2696  } else if (obj._typename === 'TGeoTrack') {
2697  if (add_objects && !this.addExtra(obj, itemname)) return false;
2698  isany = this.drawGeoTrack(obj, itemname);
2699  } else if ((obj._typename === 'TEveTrack') || (obj._typename === 'ROOT::Experimental::TEveTrack')) {
2700  if (add_objects && !this.addExtra(obj, itemname)) return false;
2701  isany = this.drawEveTrack(obj, itemname);
2702  } else if ((obj._typename === 'TEvePointSet') || (obj._typename === "ROOT::Experimental::TEvePointSet") || (obj._typename === "TPolyMarker3D")) {
2703  if (add_objects && !this.addExtra(obj, itemname)) return false;
2704  isany = this.drawHit(obj, itemname);
2705  } else if ((obj._typename === "TEveGeoShapeExtract") || (obj._typename === "ROOT::Experimental::TEveGeoShapeExtract")) {
2706  if (add_objects && !this.addExtra(obj, itemname)) return false;
2707  isany = this.drawExtraShape(obj, itemname);
2708  }
2709 
2710  if (isany && do_render) {
2711  this.updateClipping(true);
2712  this.Render3D(100);
2713  }
2714 
2715  return isany;
2716  }
2717 
2718  TGeoPainter.prototype.getExtrasContainer = function(action, name) {
2719  if (!this._toplevel) return null;
2720 
2721  if (!name) name = "tracks";
2722 
2723  var extras = null, lst = [];
2724  for (var n=0;n<this._toplevel.children.length;++n) {
2725  var chld = this._toplevel.children[n];
2726  if (!chld._extras) continue;
2727  if (action==='collect') { lst.push(chld); continue; }
2728  if (chld._extras === name) { extras = chld; break; }
2729  }
2730 
2731  if (action==='collect') {
2732  for (var k=0;k<lst.length;++k) this._toplevel.remove(lst[k]);
2733  return lst;
2734  }
2735 
2736  if (action==="delete") {
2737  if (extras) this._toplevel.remove(extras);
2738  JSROOT.Painter.DisposeThreejsObject(extras);
2739  return null;
2740  }
2741 
2742  if ((action!=="get") && !extras) {
2743  extras = new THREE.Object3D();
2744  extras._extras = name;
2745  this._toplevel.add(extras);
2746  }
2747 
2748  return extras;
2749  }
2750 
2751  TGeoPainter.prototype.drawGeoTrack = function(track, itemname) {
2752  if (!track || !track.fNpoints) return false;
2753 
2754  var track_width = track.fLineWidth || 1,
2755  track_color = JSROOT.Painter.root_colors[track.fLineColor] || "rgb(255,0,255)";
2756 
2757  if (JSROOT.browser.isWin) track_width = 1; // not supported on windows
2758 
2759  var npoints = Math.round(track.fNpoints/4),
2760  buf = new Float32Array((npoints-1)*6),
2761  pos = 0, projv = this.ctrl.projectPos,
2762  projx = (this.ctrl.project === "x"),
2763  projy = (this.ctrl.project === "y"),
2764  projz = (this.ctrl.project === "z");
2765 
2766  for (var k=0;k<npoints-1;++k) {
2767  buf[pos] = projx ? projv : track.fPoints[k*4];
2768  buf[pos+1] = projy ? projv : track.fPoints[k*4+1];
2769  buf[pos+2] = projz ? projv : track.fPoints[k*4+2];
2770  buf[pos+3] = projx ? projv : track.fPoints[k*4+4];
2771  buf[pos+4] = projy ? projv : track.fPoints[k*4+5];
2772  buf[pos+5] = projz ? projv : track.fPoints[k*4+6];
2773  pos+=6;
2774  }
2775 
2776  var lineMaterial = new THREE.LineBasicMaterial({ color: track_color, linewidth: track_width }),
2777  line = JSROOT.Painter.createLineSegments(buf, lineMaterial);
2778 
2779  line.renderOrder = 1000000; // to bring line to the front
2780  line.geo_name = itemname;
2781  line.geo_object = track;
2782  line.hightlightWidthScale = 2;
2783 
2784  if (itemname && itemname.indexOf("<prnt>/Tracks")==0)
2785  line.main_track = true;
2786 
2787  this.getExtrasContainer().add(line);
2788 
2789  return true;
2790  }
2791 
2792  TGeoPainter.prototype.drawEveTrack = function(track, itemname) {
2793  if (!track || (track.fN <= 0)) return false;
2794 
2795  var track_width = track.fLineWidth || 1,
2796  track_color = JSROOT.Painter.root_colors[track.fLineColor] || "rgb(255,0,255)";
2797 
2798  if (JSROOT.browser.isWin) track_width = 1; // not supported on windows
2799 
2800  var buf = new Float32Array((track.fN-1)*6), pos = 0,
2801  projv = this.ctrl.projectPos,
2802  projx = (this.ctrl.project === "x"),
2803  projy = (this.ctrl.project === "y"),
2804  projz = (this.ctrl.project === "z");
2805 
2806  for (var k=0;k<track.fN-1;++k) {
2807  buf[pos] = projx ? projv : track.fP[k*3];
2808  buf[pos+1] = projy ? projv : track.fP[k*3+1];
2809  buf[pos+2] = projz ? projv : track.fP[k*3+2];
2810  buf[pos+3] = projx ? projv : track.fP[k*3+3];
2811  buf[pos+4] = projy ? projv : track.fP[k*3+4];
2812  buf[pos+5] = projz ? projv : track.fP[k*3+5];
2813  pos+=6;
2814  }
2815 
2816  var lineMaterial = new THREE.LineBasicMaterial({ color: track_color, linewidth: track_width }),
2817  line = JSROOT.Painter.createLineSegments(buf, lineMaterial);
2818 
2819  line.renderOrder = 1000000; // to bring line to the front
2820  line.geo_name = itemname;
2821  line.geo_object = track;
2822  line.hightlightWidthScale = 2;
2823 
2824  this.getExtrasContainer().add(line);
2825 
2826  return true;
2827  }
2828 
2831  TGeoPainter.prototype.drawHit = function(hit, itemname) {
2832  if (!hit || !hit.fN || (hit.fN < 0)) return false;
2833 
2834  // make hit size scaling factor of overall geometry size
2835  // otherwise it is not possible to correctly see hits at all
2836  var hit_size = hit.fMarkerSize * this.getOverallSize() * 0.005;
2837  if (hit_size <= 0) hit_size = 1;
2838 
2839  var size = hit.fN,
2840  projv = this.ctrl.projectPos,
2841  projx = (this.ctrl.project === "x"),
2842  projy = (this.ctrl.project === "y"),
2843  projz = (this.ctrl.project === "z"),
2844  pnts = new JSROOT.Painter.PointsCreator(size, this._webgl, hit_size);
2845 
2846  for (var i=0;i<size;i++)
2847  pnts.AddPoint(projx ? projv : hit.fP[i*3],
2848  projy ? projv : hit.fP[i*3+1],
2849  projz ? projv : hit.fP[i*3+2]);
2850 
2851  var mesh = pnts.CreatePoints({ color: JSROOT.Painter.root_colors[hit.fMarkerColor] || "rgb(0,0,255)",
2852  style: hit.fMarkerStyle,
2853  callback: function(delayed) { if (delayed) this.Render3D(100); }.bind(this) });
2854 
2855  mesh.renderOrder = 1000000; // to bring points to the front
2856  mesh.highlightScale = 2;
2857 
2858  mesh.geo_name = itemname;
2859  mesh.geo_object = hit;
2860 
2861  this.getExtrasContainer().add(mesh);
2862 
2863  return true;
2864  }
2865 
2866  TGeoPainter.prototype.drawExtraShape = function(obj, itemname) {
2867  var toplevel = JSROOT.GEO.build(obj);
2868  if (!toplevel) return false;
2869 
2870  toplevel.geo_name = itemname;
2871  toplevel.geo_object = obj;
2872 
2873  this.getExtrasContainer().add(toplevel);
2874  return true;
2875  }
2876 
2877  TGeoPainter.prototype.FindNodeWithVolume = function(name, action, prnt, itemname, volumes) {
2878 
2879  var first_level = false, res = null;
2880 
2881  if (!prnt) {
2882  prnt = this.GetGeometry();
2883  if (!prnt && (JSROOT.GEO.NodeKind(prnt)!==0)) return null;
2884  itemname = this.geo_manager ? prnt.fName : "";
2885  first_level = true;
2886  volumes = [];
2887  } else {
2888  if (itemname.length>0) itemname += "/";
2889  itemname += prnt.fName;
2890  }
2891 
2892  if (!prnt.fVolume || prnt.fVolume._searched) return null;
2893 
2894  if (name.test(prnt.fVolume.fName)) {
2895  res = action({ node: prnt, item: itemname });
2896  if (res) return res;
2897  }
2898 
2899  prnt.fVolume._searched = true;
2900  volumes.push(prnt.fVolume);
2901 
2902  if (prnt.fVolume.fNodes)
2903  for (var n=0;n<prnt.fVolume.fNodes.arr.length;++n) {
2904  res = this.FindNodeWithVolume(name, action, prnt.fVolume.fNodes.arr[n], itemname, volumes);
2905  if (res) break;
2906  }
2907 
2908  if (first_level)
2909  for (var n=0, len=volumes.length; n<len; ++n)
2910  delete volumes[n]._searched;
2911 
2912  return res;
2913  }
2914 
2916  TGeoPainter.prototype.checkScript = function(script_name, call_back) {
2917 
2918  var painter = this, draw_obj = this.GetGeometry(), name_prefix = "";
2919 
2920  if (this.geo_manager) name_prefix = draw_obj.fName;
2921 
2922  if (!script_name || (script_name.length<3) || (JSROOT.GEO.NodeKind(draw_obj)!==0))
2923  return JSROOT.CallBack(call_back, draw_obj, name_prefix);
2924 
2925  var mgr = {
2926  GetVolume: function (name) {
2927  var regexp = new RegExp("^"+name+"$");
2928  var currnode = painter.FindNodeWithVolume(regexp, function(arg) { return arg; } );
2929 
2930  if (!currnode) console.log('Did not found '+name + ' volume');
2931 
2932  // return proxy object with several methods, typically used in ROOT geom scripts
2933  return {
2934  found: currnode,
2935  fVolume: currnode ? currnode.node.fVolume : null,
2936  InvisibleAll: function(flag) {
2937  JSROOT.GEO.InvisibleAll.call(this.fVolume, flag);
2938  },
2939  Draw: function() {
2940  if (!this.found || !this.fVolume) return;
2941  draw_obj = this.found.node;
2942  name_prefix = this.found.item;
2943  console.log('Select volume for drawing', this.fVolume.fName, name_prefix);
2944  },
2945  SetTransparency: function(lvl) {
2946  if (this.fVolume && this.fVolume.fMedium && this.fVolume.fMedium.fMaterial)
2947  this.fVolume.fMedium.fMaterial.fFillStyle = 3000+lvl;
2948  },
2949  SetLineColor: function(col) {
2950  if (this.fVolume) this.fVolume.fLineColor = col;
2951  }
2952  };
2953  },
2954 
2955  DefaultColors: function() {
2956  painter.ctrl.dflt_colors = true;
2957  },
2958 
2959  SetMaxVisNodes: function(limit) {
2960  if (!painter.ctrl.maxnodes)
2961  painter.ctrl.maxnodes = pasrseInt(limit) || 0;
2962  },
2963 
2964  SetVisLevel: function(limit) {
2965  if (!painter.ctrl.vislevel)
2966  painter.ctrl.vislevel = parseInt(limit) || 0;
2967  }
2968  };
2969 
2970  JSROOT.progress('Loading macro ' + script_name);
2971 
2972  JSROOT.NewHttpRequest(script_name, "text", function(res) {
2973  if (!res || (res.length==0))
2974  return JSROOT.CallBack(call_back, draw_obj, name_prefix);
2975 
2976  var lines = res.split('\n');
2977 
2978  ProcessNextLine(0);
2979 
2980  function ProcessNextLine(indx) {
2981 
2982  var first_tm = new Date().getTime();
2983  while (indx < lines.length) {
2984  var line = lines[indx++].trim();
2985 
2986  if (line.indexOf('//')==0) continue;
2987 
2988  if (line.indexOf('gGeoManager')<0) continue;
2989  line = line.replace('->GetVolume','.GetVolume');
2990  line = line.replace('->InvisibleAll','.InvisibleAll');
2991  line = line.replace('->SetMaxVisNodes','.SetMaxVisNodes');
2992  line = line.replace('->DefaultColors','.DefaultColors');
2993  line = line.replace('->Draw','.Draw');
2994  line = line.replace('->SetTransparency','.SetTransparency');
2995  line = line.replace('->SetLineColor','.SetLineColor');
2996  line = line.replace('->SetVisLevel','.SetVisLevel');
2997  if (line.indexOf('->')>=0) continue;
2998 
2999  // console.log(line);
3000 
3001  try {
3002  var func = new Function('gGeoManager',line);
3003  func(mgr);
3004  } catch(err) {
3005  console.error('Problem by processing ' + line);
3006  }
3007 
3008  var now = new Date().getTime();
3009  if (now - first_tm > 300) {
3010  JSROOT.progress('exec ' + line);
3011  return setTimeout(ProcessNextLine.bind(this,indx),1);
3012  }
3013  }
3014 
3015  JSROOT.CallBack(call_back, draw_obj, name_prefix);
3016  }
3017 
3018  }).send();
3019  }
3020 
3023  TGeoPainter.prototype.assignClones = function(clones) {
3024  this._clones_owner = true;
3025  this._clones = clones;
3026  }
3027 
3030  TGeoPainter.prototype.prepareObjectDraw = function(draw_obj, name_prefix) {
3031 
3032  // if did cleanup - ignore all kind of activity
3033  if (this.did_cleanup)
3034  return;
3035 
3036  if (name_prefix == "__geom_viewer_append__") {
3037  this._new_append_nodes = draw_obj;
3038  this.ctrl.use_worker = 0;
3039  this._geom_viewer = true; // indicate that working with geom viewer
3040  } else if ((name_prefix == "__geom_viewer_selection__") && this._clones) {
3041  // these are selection done from geom viewer
3042  this._new_draw_nodes = draw_obj;
3043  this.ctrl.use_worker = 0;
3044  this._geom_viewer = true; // indicate that working with geom viewer
3045  } else if (this._main_painter) {
3046 
3047  this._clones_owner = false;
3048 
3049  this._clones = this._main_painter._clones;
3050 
3051  console.log('Reuse clones', this._clones.nodes.length, 'from main painter');
3052 
3053  } else if (!draw_obj) {
3054 
3055  this._clones_owner = false;
3056 
3057  this._clones = null;
3058 
3059  } else {
3060 
3061  this._start_drawing_time = new Date().getTime();
3062 
3063  this._clones_owner = true;
3064 
3065  this._clones = new JSROOT.GEO.ClonedNodes(draw_obj);
3066 
3067  var lvl = this.ctrl.vislevel, maxnodes = this.ctrl.maxnodes;
3068  if (this.geo_manager) {
3069  if (!lvl && this.geo_manager.fVisLevel)
3070  lvl = this.geo_manager.fVisLevel;
3071  if (!maxnodes)
3072  maxnodes = this.geo_manager.fMaxVisNodes;
3073  }
3074 
3075  this._clones.SetVisLevel(lvl);
3076  this._clones.SetMaxVisNodes(maxnodes);
3077 
3078  this._clones.name_prefix = name_prefix;
3079 
3080  var hide_top_volume = !!this.geo_manager && !this.ctrl.showtop;
3081 
3082  var uniquevis = this.ctrl.no_screen ? 0 : this._clones.MarkVisibles(true, false, hide_top_volume);
3083 
3084  if (uniquevis <= 0)
3085  uniquevis = this._clones.MarkVisibles(false, false, hide_top_volume);
3086  else
3087  uniquevis = this._clones.MarkVisibles(true, true, hide_top_volume); // copy bits once and use normal visibility bits
3088 
3089  this._clones.ProduceIdShits();
3090 
3091  var spent = new Date().getTime() - this._start_drawing_time;
3092 
3093  if (!this._scene)
3094  console.log('Creating clones', this._clones.nodes.length, 'takes', spent, 'uniquevis', uniquevis);
3095 
3096  if (this.options._count)
3097  return this.drawCount(uniquevis, spent);
3098  }
3099 
3100  if (!this._scene) {
3101 
3102  // this is limit for the visible faces, number of volumes does not matter
3103  this.ctrl.maxlimit = (this._webgl ? 200000 : 100000) * this.ctrl.more;
3104 
3105  this._first_drawing = true;
3106 
3107  // activate worker
3108  if (this.ctrl.use_worker > 0) this.startWorker();
3109 
3110  var size = this.size_for_3d(this._usesvg ? 3 : undefined);
3111 
3112  this._fit_main_area = (size.can3d === -1);
3113 
3114  var dom = this.createScene(size.width, size.height);
3115 
3116  this.add_3d_canvas(size, dom);
3117 
3118  // set top painter only when first child exists
3119  this.AccessTopPainter(true);
3120  }
3121 
3122  this.CreateToolbar();
3123 
3124  if (this._clones) {
3125  this.showDrawInfo("Drawing geometry");
3126  this.startDrawGeometry(true);
3127  } else {
3128  this.completeDraw();
3129  }
3130  }
3131 
3132  TGeoPainter.prototype.showDrawInfo = function(msg) {
3133  // methods show info when first geometry drawing is performed
3134 
3135  if (!this._first_drawing || !this._start_drawing_time) return;
3136 
3137  var main = this._renderer.domElement.parentNode,
3138  info = d3.select(main).select(".geo_info");
3139 
3140  if (!msg) {
3141  info.remove();
3142  } else {
3143  var spent = (new Date().getTime() - this._start_drawing_time)*1e-3;
3144  if (info.empty()) info = d3.select(main).append("p").attr("class","geo_info");
3145  info.html(msg + ", " + spent.toFixed(1) + "s");
3146  }
3147 
3148  }
3149 
3150  TGeoPainter.prototype.continueDraw = function() {
3151 
3152  // nothing to do - exit
3153  if (this.drawing_stage === 0) return;
3154 
3155  var tm0 = new Date().getTime(),
3156  interval = this._first_drawing ? 1000 : 200,
3157  now = tm0;
3158 
3159  while(true) {
3160 
3161  var res = this.nextDrawAction();
3162 
3163  if (!res) break;
3164 
3165  now = new Date().getTime();
3166 
3167  // stop creation after 100 sec, render as is
3168  if (now - this._startm > 1e5) {
3169  this.drawing_stage = 0;
3170  break;
3171  }
3172 
3173  // if we are that fast, do next action
3174  if ((res === true) && (now - tm0 < interval)) continue;
3175 
3176  if ((now - tm0 > interval) || (res === 1) || (res === 2)) {
3177 
3178  JSROOT.progress(this.drawing_log);
3179 
3180  this.showDrawInfo(this.drawing_log);
3181 
3182  if (this._first_drawing && this._webgl && (this._num_meshes - this._last_render_meshes > 100) && (now - this._last_render_tm > 2.5*interval)) {
3183  this.adjustCameraPosition();
3184  this.Render3D(-1);
3185  this._last_render_meshes = this.ctrl.info.num_meshes;
3186  }
3187  if (res !== 2) setTimeout(this.continueDraw.bind(this), (res === 1) ? 100 : 1);
3188 
3189  return;
3190  }
3191  }
3192 
3193  var take_time = now - this._startm;
3194 
3195  if (this._first_drawing)
3196  JSROOT.console('Create tm = ' + take_time + ' meshes ' + this.ctrl.info.num_meshes + ' faces ' + this.ctrl.info.num_faces);
3197 
3198  if (take_time > 300) {
3199  JSROOT.progress('Rendering geometry');
3200  this.showDrawInfo("Rendering");
3201  return setTimeout(this.completeDraw.bind(this, true), 10);
3202  }
3203 
3204  this.completeDraw(true);
3205  }
3206 
3211  TGeoPainter.prototype.TestCameraPosition = function(force) {
3212  this._camera.updateMatrixWorld();
3213  var origin = this._camera.position.clone();
3214 
3215  if (!force && this._last_camera_position) {
3216  // if camera position does not changed a lot, ignore such change
3217  var dist = this._last_camera_position.distanceTo(origin);
3218  if (dist < (this._overall_size || 1000)*1e-4) return;
3219  }
3220 
3221  this._last_camera_position = origin; // remember current camera position
3222 
3223  if (!this.ctrl.project && this._webgl)
3224  JSROOT.GEO.produceRenderOrder(this._toplevel, origin, this.ctrl.depthMethod, this._clones);
3225  }
3226 
3235  TGeoPainter.prototype.Render3D = function(tmout, measure) {
3236 
3237  if (!this._renderer) {
3238  console.warn('renderer object not exists - check code');
3239  return;
3240  }
3241 
3242  if (tmout === undefined) tmout = 5; // by default, rendering happens with timeout
3243 
3244  if ((tmout <= 0) || this._usesvg) {
3245  if ('render_tmout' in this) {
3246  clearTimeout(this.render_tmout);
3247  } else {
3248  if (tmout === -2222) return; // special case to check if rendering timeout was active
3249  }
3250 
3251  var tm1 = new Date();
3252 
3253  this.TestCameraPosition(tmout === -1);
3254 
3255  // its needed for outlinePass - do rendering, most consuming time
3256  if (this._webgl && this._effectComposer && (this._effectComposer.passes.length > 0)) {
3257  this._effectComposer.render();
3258  } else {
3259  // this._renderer.logarithmicDepthBuffer = true;
3260  this._renderer.render(this._scene, this._camera);
3261  }
3262 
3263  var tm2 = new Date();
3264 
3265  this.last_render_tm = tm2.getTime();
3266 
3267  delete this.render_tmout;
3268 
3269  if ((this.first_render_tm === 0) && measure) {
3270  this.first_render_tm = tm2.getTime() - tm1.getTime();
3271  JSROOT.console('First render tm = ' + this.first_render_tm);
3272  }
3273 
3274  return JSROOT.Painter.AfterRender3D(this._renderer);
3275  }
3276 
3277  // do not shoot timeout many times
3278  if (!this.render_tmout)
3279  this.render_tmout = setTimeout(this.Render3D.bind(this,0,measure), tmout);
3280  }
3281 
3282 
3283  TGeoPainter.prototype.startWorker = function() {
3284 
3285  if (this._worker) return;
3286 
3287  this._worker_ready = false;
3288  this._worker_jobs = 0; // counter how many requests send to worker
3289 
3290  var pthis = this;
3291 
3292  this._worker = new Worker(JSROOT.source_dir + "scripts/JSRootGeoWorker.js");
3293 
3294  this._worker.onmessage = function(e) {
3295 
3296  if (typeof e.data !== 'object') return;
3297 
3298  if ('log' in e.data)
3299  return JSROOT.console('geo: ' + e.data.log);
3300 
3301  if ('progress' in e.data)
3302  return JSROOT.progress(e.data.progress);
3303 
3304  e.data.tm3 = new Date().getTime();
3305 
3306  if ('init' in e.data) {
3307  pthis._worker_ready = true;
3308  return JSROOT.console('Worker ready: ' + (e.data.tm3 - e.data.tm0));
3309  }
3310 
3311  pthis.processWorkerReply(e.data);
3312  };
3313 
3314  // send initialization message with clones
3315  this._worker.postMessage( {
3316  init: true, // indicate init command for worker
3317  browser: JSROOT.browser,
3318  tm0: new Date().getTime(),
3319  vislevel: this._clones.GetVisLevel(),
3320  maxvisnodes: this._clones.GetMaxVisNodes(),
3321  clones: this._clones.nodes,
3322  sortmap: this._clones.sortmap } );
3323  }
3324 
3325  TGeoPainter.prototype.canSubmitToWorker = function(force) {
3326  if (!this._worker) return false;
3327 
3328  return this._worker_ready && ((this._worker_jobs == 0) || force);
3329  }
3330 
3331  TGeoPainter.prototype.submitToWorker = function(job) {
3332  if (!this._worker) return false;
3333 
3334  this._worker_jobs++;
3335 
3336  job.tm0 = new Date().getTime();
3337 
3338  this._worker.postMessage(job);
3339  }
3340 
3341  TGeoPainter.prototype.processWorkerReply = function(job) {
3342  this._worker_jobs--;
3343 
3344  if ('collect' in job) {
3345  this._new_draw_nodes = job.new_nodes;
3346  this._draw_all_nodes = job.complete;
3347  this.drawing_stage = 3;
3348  // invoke methods immediately
3349  return this.continueDraw();
3350  }
3351 
3352  if ('shapes' in job) {
3353 
3354  for (var n=0;n<job.shapes.length;++n) {
3355  var item = job.shapes[n],
3356  origin = this._build_shapes[n];
3357 
3358  // var shape = this._clones.GetNodeShape(item.nodeid);
3359 
3360  if (item.buf_pos && item.buf_norm) {
3361  if (item.buf_pos.length === 0) {
3362  origin.geom = null;
3363  } else if (item.buf_pos.length !== item.buf_norm.length) {
3364  console.error('item.buf_pos',item.buf_pos.length, 'item.buf_norm', item.buf_norm.length);
3365  origin.geom = null;
3366  } else {
3367  origin.geom = new THREE.BufferGeometry();
3368 
3369  origin.geom.addAttribute( 'position', new THREE.BufferAttribute( item.buf_pos, 3 ) );
3370  origin.geom.addAttribute( 'normal', new THREE.BufferAttribute( item.buf_norm, 3 ) );
3371  }
3372 
3373  origin.ready = true;
3374  origin.nfaces = item.nfaces;
3375  }
3376  }
3377 
3378  job.tm4 = new Date().getTime();
3379 
3380  // console.log('Get reply from worker', job.tm3-job.tm2, ' decode json in ', job.tm4-job.tm3);
3381 
3382  this.drawing_stage = 7; // first check which shapes are used, than build meshes
3383 
3384  // invoke methods immediately
3385  return this.continueDraw();
3386  }
3387  }
3388 
3389  TGeoPainter.prototype.testGeomChanges = function() {
3390  if (this._main_painter) {
3391  console.warn('Get testGeomChanges call for slave painter');
3392  return this._main_painter.testGeomChanges();
3393  }
3394  this.startDrawGeometry();
3395  for (var k=0;k<this._slave_painters.length;++k)
3396  this._slave_painters[k].startDrawGeometry();
3397  }
3398 
3400  TGeoPainter.prototype.drawSimpleAxis = function(norender) {
3401  this.getExtrasContainer('delete', 'axis');
3402 
3403  if (!this.ctrl._axis)
3404  return norender ? null : this.Render3D();
3405 
3406  var box = this.getGeomBoundingBox(this._toplevel);
3407 
3408  var container = this.getExtrasContainer('create', 'axis');
3409 
3410  var text_size = 0.02 * Math.max( (box.max.x - box.min.x), (box.max.y - box.min.y), (box.max.z - box.min.z)),
3411  center = [0,0,0],
3412  names = ['x','y','z'],
3413  labels = ['X','Y','Z'],
3414  colors = ["red","green","blue"],
3415  ortho = this.ctrl.ortho_camera,
3416  yup = [this.ctrl._yup, this.ctrl._yup, this.ctrl._yup],
3417  numaxis = 3;
3418 
3419  if (this.ctrl._axis == 2)
3420  for (var naxis=0;naxis<3;++naxis) {
3421  var name = names[naxis];
3422  if ((box.min[name]<=0) && (box.max[name]>=0)) continue;
3423  center[naxis] = (box.min[name] + box.max[name])/2;
3424  }
3425 
3426  // only two dimensions are seen by ortho camera, X draws Z, can be configured better later
3427  if (this.ctrl.ortho_camera) {
3428  numaxis = 2;
3429  labels[0] = labels[2];
3430  colors[0] = colors[2];
3431  yup[0] = yup[2];
3432  ortho = true;
3433  }
3434 
3435  for (var naxis=0;naxis<numaxis;++naxis) {
3436 
3437  var buf = new Float32Array(6), axiscol = colors[naxis], name = names[naxis];
3438 
3439  function Convert(value) {
3440  var range = box.max[name] - box.min[name];
3441  if (range<2) return value.toFixed(3);
3442  if (Math.abs(value)>1e5) return value.toExponential(3);
3443  return Math.round(value).toString();
3444  }
3445 
3446  var lbl = Convert(box.max[name]);
3447 
3448  buf[0] = box.min.x;
3449  buf[1] = box.min.y;
3450  buf[2] = box.min.z;
3451 
3452  buf[3] = box.min.x;
3453  buf[4] = box.min.y;
3454  buf[5] = box.min.z;
3455 
3456  switch (naxis) {
3457  case 0: buf[3] = box.max.x; if (yup[0] && !ortho) lbl = labels[0] + " " + lbl; else lbl += " " + labels[0]; break;
3458  case 1: buf[4] = box.max.y; if (yup[1]) lbl += " " + labels[1]; else lbl = labels[1] + " " + lbl; break;
3459  case 2: buf[5] = box.max.z; lbl += " " + labels[2]; break;
3460  }
3461 
3462  if (this.ctrl._axis == 2)
3463  for (var k=0;k<6;++k)
3464  if ((k % 3) !== naxis) buf[k] = center[k%3];
3465 
3466  var lineMaterial = new THREE.LineBasicMaterial({ color: axiscol }),
3467  mesh = JSROOT.Painter.createLineSegments(buf, lineMaterial);
3468 
3469  container.add(mesh);
3470 
3471  var textMaterial = new THREE.MeshBasicMaterial({ color: axiscol });
3472 
3473  if ((center[naxis]===0) && (center[naxis]>=box.min[name]) && (center[naxis]<=box.max[name]))
3474  if ((this.ctrl._axis != 2) || (naxis===0)) {
3475  var geom = ortho ? new THREE.CircleBufferGeometry(text_size*0.25) :
3476  new THREE.SphereBufferGeometry(text_size*0.25);
3477  mesh = new THREE.Mesh(geom, textMaterial);
3478  mesh.translateX((naxis===0) ? center[0] : buf[0]);
3479  mesh.translateY((naxis===1) ? center[1] : buf[1]);
3480  mesh.translateZ((naxis===2) ? center[2] : buf[2]);
3481  container.add(mesh);
3482  }
3483 
3484  var text3d = new THREE.TextGeometry(lbl, { font: JSROOT.threejs_font_helvetiker_regular, size: text_size, height: 0, curveSegments: 5 });
3485  mesh = new THREE.Mesh(text3d, textMaterial);
3486  var textbox = new THREE.Box3().setFromObject(mesh);
3487 
3488  mesh.translateX(buf[3]);
3489  mesh.translateY(buf[4]);
3490  mesh.translateZ(buf[5]);
3491 
3492  if (yup[naxis]) {
3493  switch (naxis) {
3494  case 0:
3495  if (!ortho) {
3496  mesh.rotateY(Math.PI);
3497  mesh.translateX(-textbox.max.x-text_size*0.5);
3498  } else {
3499  mesh.translateX(text_size*0.5);
3500  }
3501  mesh.translateY(-textbox.max.y/2);
3502  break;
3503  case 1:
3504  if (!ortho) {
3505  mesh.rotateX(-Math.PI/2);
3506  mesh.rotateY(-Math.PI/2);
3507  } else {
3508  mesh.rotateZ(Math.PI/2);
3509  }
3510  mesh.translateX(text_size*0.5);
3511  mesh.translateY(-textbox.max.y/2);
3512  break;
3513  case 2: mesh.rotateY(-Math.PI/2); mesh.translateX(text_size*0.5); mesh.translateY(-textbox.max.y/2); break;
3514  }
3515  } else {
3516  switch (naxis) {
3517  case 0: mesh.rotateX(Math.PI/2); mesh.translateY(-textbox.max.y/2); mesh.translateX(text_size*0.5); break;
3518  case 1: mesh.rotateX(Math.PI/2); mesh.rotateY(-Math.PI/2); mesh.translateX(-textbox.max.x-text_size*0.5); mesh.translateY(-textbox.max.y/2); break;
3519  case 2: mesh.rotateX(Math.PI/2); mesh.rotateZ(Math.PI/2); mesh.translateX(text_size*0.5); mesh.translateY(-textbox.max.y/2); break;
3520  }
3521  }
3522 
3523  container.add(mesh);
3524 
3525  text3d = new THREE.TextGeometry(Convert(box.min[name]), { font: JSROOT.threejs_font_helvetiker_regular, size: text_size, height: 0, curveSegments: 5 });
3526 
3527  mesh = new THREE.Mesh(text3d, textMaterial);
3528  textbox = new THREE.Box3().setFromObject(mesh);
3529 
3530  mesh.translateX(buf[0]);
3531  mesh.translateY(buf[1]);
3532  mesh.translateZ(buf[2]);
3533 
3534  if (yup[naxis]) {
3535  switch (naxis) {
3536  case 0:
3537  if (!ortho) {
3538  mesh.rotateY(Math.PI);
3539  mesh.translateX(text_size*0.5);
3540  } else {
3541  mesh.translateX(-textbox.max.x-text_size*0.5);
3542  }
3543  mesh.translateY(-textbox.max.y/2);
3544  break;
3545  case 1:
3546  if (!ortho) {
3547  mesh.rotateX(-Math.PI/2);
3548  mesh.rotateY(-Math.PI/2);
3549  } else {
3550  mesh.rotateZ(Math.PI/2);
3551  }
3552  mesh.translateY(-textbox.max.y/2);
3553  mesh.translateX(-textbox.max.x-text_size*0.5);
3554  break;
3555  case 2: mesh.rotateY(-Math.PI/2); mesh.translateX(-textbox.max.x-text_size*0.5); mesh.translateY(-textbox.max.y/2); break;
3556  }
3557  } else {
3558  switch (naxis) {
3559  case 0: mesh.rotateX(Math.PI/2); mesh.translateX(-textbox.max.x-text_size*0.5); mesh.translateY(-textbox.max.y/2); break;
3560  case 1: mesh.rotateX(Math.PI/2); mesh.rotateY(-Math.PI/2); mesh.translateY(-textbox.max.y/2); mesh.translateX(text_size*0.5); break;
3561  case 2: mesh.rotateX(Math.PI/2); mesh.rotateZ(Math.PI/2); mesh.translateX(-textbox.max.x-text_size*0.5); mesh.translateY(-textbox.max.y/2); break;
3562  }
3563  }
3564 
3565  container.add(mesh);
3566  }
3567 
3568  // after creating axes trigger rendering and recalculation of depth
3569  this.changedDepthMethod(norender ? "norender" : undefined);
3570  }
3571 
3573  TGeoPainter.prototype.toggleAxesDraw = function() {
3574  this.setAxesDraw("toggle");
3575  }
3576 
3578  TGeoPainter.prototype.setAxesDraw = function(on) {
3579  if (on === "toggle")
3580  this.ctrl._axis = this.ctrl._axis ? 0 : 1;
3581  else
3582  this.ctrl._axis = (typeof on == 'number') ? on : (on ? 1 : 0);
3583  this.drawSimpleAxis();
3584  }
3585 
3587  TGeoPainter.prototype.setAutoRotate = function(on) {
3588  if (this.ctrl.project) return;
3589  if (on !== undefined) this.ctrl.rotate = on;
3590  this.autorotate(2.5);
3591  }
3592 
3594  TGeoPainter.prototype.toggleWireFrame = function() {
3595  this.ctrl.wireframe = !this.ctrl.wireframe;
3596  this.changedWireFrame();
3597  }
3598 
3600  TGeoPainter.prototype.setWireFrame = function(on) {
3601  this.ctrl.wireframe = on ? true : false;
3602  this.changedWireFrame();
3603  }
3604 
3606  TGeoPainter.prototype.setShowTop = function(on) {
3607  this.ctrl.showtop = on ? true : false;
3608  this.RedrawObject('same');
3609  }
3610 
3612  TGeoPainter.prototype.changedClipping = function(naxis) {
3613  var clip = this.ctrl.clip;
3614 
3615  if ((naxis !== undefined) && (naxis >= 0)) {
3616  if (!clip[naxis].enabled) return;
3617  }
3618 
3619  if (clip[0].enabled || clip[1].enabled || clip[2].enabled) {
3620  this.ctrl.ssao.enabled = false;
3621  this.removeSSAO();
3622  }
3623 
3624  this.updateClipping(false, true);
3625  }
3626 
3628  TGeoPainter.prototype.changedDepthTest = function() {
3629  if (!this._toplevel) return;
3630  var flag = this.ctrl.depthTest;
3631  this._toplevel.traverse( function (node) {
3632  if (node instanceof THREE.Mesh) {
3633  node.material.depthTest = flag;
3634  }
3635  });
3636 
3637  this.Render3D(0);
3638  }
3639 
3641  TGeoPainter.prototype.changedDepthMethod = function(arg) {
3642  // force recalculatiion of render order
3643  delete this._last_camera_position;
3644  if (arg !== "norender") this.Render3D();
3645  }
3646 
3648  TGeoPainter.prototype.changedHighlight = function() {
3649  if (!this.ctrl.highlight)
3650  this.HighlightMesh(null);
3651  }
3652 
3654  TGeoPainter.prototype.updateClipping = function(without_render, force_traverse) {
3655  if (!this._webgl) return;
3656 
3657  var clip = this.ctrl.clip, panels = [], changed = false,
3658  constants = [ clip[0].value, -1 * clip[1].value, (this.ctrl._yup ? -1 : 1) * clip[2].value ],
3659  clip_cfg = this.ctrl.clipIntersect ? 16 : 0;
3660 
3661  for (var k=0;k<3;++k) {
3662  if (clip[k].enabled) clip_cfg += 2 << k;
3663  if (this._clipPlanes[k].constant != constants[k]) {
3664  changed = true;
3665  this._clipPlanes[k].constant = constants[k];
3666  }
3667  }
3668 
3669  if (!this.ctrl.ssao.enabled) {
3670  if (clip[0].enabled) panels.push(this._clipPlanes[0]);
3671  if (clip[1].enabled) panels.push(this._clipPlanes[1]);
3672  if (clip[2].enabled) panels.push(this._clipPlanes[2]);
3673  clip_cfg += panels.length*1000;
3674  }
3675  if (panels.length == 0) panels = null;
3676 
3677  if (this._clipCfg !== clip_cfg) changed = true;
3678 
3679  this._clipCfg = clip_cfg;
3680 
3681  var any_clipping = !!panels, ci = this.ctrl.clipIntersect,
3682  material_side = any_clipping ? THREE.DoubleSide : THREE.FrontSide;
3683 
3684  if (force_traverse || changed)
3685  this._scene.traverse( function (node) {
3686  if (node.hasOwnProperty("material") && node.material && (node.material.clippingPlanes !== undefined)) {
3687 
3688  if (node.material.clippingPlanes !== panels) {
3689  node.material.clipIntersection = ci;
3690  node.material.clippingPlanes = panels;
3691  node.material.needsUpdate = true;
3692  }
3693 
3694  if (node.material.emissive !== undefined) {
3695  if (node.material.side != material_side) {
3696  node.material.side = material_side;
3697  node.material.needsUpdate = true;
3698  }
3699  }
3700  }
3701  });
3702 
3703  this.ctrl.bothSides = any_clipping;
3704 
3705  if (!without_render) this.Render3D(0);
3706 
3707  return changed;
3708  }
3709 
3710  TGeoPainter.prototype.setCompleteHandler = function(callback) {
3711  this._complete_handler = callback;
3712  }
3713 
3715  TGeoPainter.prototype.completeDraw = function(close_progress) {
3716 
3717  var first_time = false, full_redraw = false, check_extras = true;
3718 
3719  if (!this.ctrl) {
3720  console.warn('ctrl object does not exist in completeDraw - something went wrong');
3721  return;
3722  }
3723 
3724  if (!this._clones) {
3725  check_extras = false;
3726  // if extra object where append, redraw them at the end
3727  this.getExtrasContainer("delete"); // delete old container
3728  var extras = (this._main_painter ? this._main_painter._extraObjects : null) || this._extraObjects;
3729  this.drawExtras(extras, "", false);
3730  } else if (this._first_drawing || this._full_redrawing) {
3731  if (this.ctrl.tracks && this.geo_manager)
3732  this.drawExtras(this.geo_manager.fTracks, "<prnt>/Tracks");
3733  }
3734 
3735  if (this._full_redrawing) {
3736  this._full_redrawing = false;
3737  full_redraw = true;
3738  this.changedDepthMethod("norender");
3739  }
3740 
3741  if (this._first_drawing) {
3742  this.adjustCameraPosition(true);
3743  this.showDrawInfo();
3744  this._first_drawing = false;
3745  first_time = true;
3746  full_redraw = true;
3747  }
3748 
3749  if (this.ctrl.transparency!==0)
3750  this.changedGlobalTransparency(this.ctrl.transparency, true);
3751 
3752  if (first_time)
3753  this.completeScene();
3754 
3755  if (full_redraw && (this.ctrl.trans_radial || this.ctrl.trans_z))
3756  this.changedTransformation("norender");
3757 
3758  if (full_redraw && this.ctrl._axis)
3759  this.drawSimpleAxis(true);
3760 
3761  this._scene.overrideMaterial = null;
3762 
3763  if (this._provided_more_nodes !== undefined) {
3764  this.appendMoreNodes(this._provided_more_nodes, true);
3765  delete this._provided_more_nodes;
3766  }
3767 
3768  if (check_extras) {
3769  // if extra object where append, redraw them at the end
3770  this.getExtrasContainer("delete"); // delete old container
3771  var extras = (this._main_painter ? this._main_painter._extraObjects : null) || this._extraObjects;
3772  this.drawExtras(extras, "", false);
3773  }
3774 
3775  this.updateClipping(true); // do not render
3776 
3777  this.Render3D(0, true);
3778 
3779  if (close_progress) JSROOT.progress();
3780 
3781  this.addOrbitControls();
3782 
3783  this.addTransformControl();
3784 
3785  if (first_time) {
3786 
3787  // after first draw check if highlight can be enabled
3788  if (this.ctrl.highlight === false)
3789  this.ctrl.highlight = (this.first_render_tm < 1000);
3790 
3791  // also highlight of scene object can be assigned at the first draw
3792  if (this.ctrl.highlight_scene === false)
3793  this.ctrl.highlight_scene = this.ctrl.highlight;
3794 
3795  // if rotation was enabled, do it
3796  if (this._webgl && this.ctrl.rotate && !this.ctrl.project) this.autorotate(2.5);
3797  if (!this._usesvg && this.ctrl.show_controls && !JSROOT.BatchMode) this.showControlOptions(true);
3798  }
3799 
3800  // call it every time, in reality invoked only first time
3801  this.DrawingReady();
3802 
3803  if (typeof this._complete_handler == 'function')
3804  this._complete_handler(this);
3805 
3806  if (this._draw_nodes_again)
3807  return this.startDrawGeometry(); // relaunch drawing
3808 
3809  this._drawing_ready = true; // indicate that drawing is completed
3810  }
3811 
3813  TGeoPainter.prototype.isDrawingReady = function() {
3814  return this._drawing_ready || false;
3815  }
3816 
3818  TGeoPainter.prototype.RemoveDrawnNode = function(nodeid) {
3819  if (!this._draw_nodes) return;
3820 
3821  var new_nodes = [];
3822 
3823  for (var n = 0; n < this._draw_nodes.length; ++n) {
3824  var entry = this._draw_nodes[n];
3825  if ((entry.nodeid === nodeid) || this._clones.IsNodeInStack(nodeid, entry.stack)) {
3826  this._clones.CreateObject3D(entry.stack, this._toplevel, 'delete_mesh');
3827  } else {
3828  new_nodes.push(entry);
3829  }
3830  }
3831 
3832  if (new_nodes.length < this._draw_nodes.length) {
3833  this._draw_nodes = new_nodes;
3834  this.Render3D();
3835  }
3836  }
3837 
3838  TGeoPainter.prototype.Cleanup = function(first_time) {
3839 
3840  if (!first_time) {
3841 
3842  this.removeSSAO();
3843 
3844  this.AccessTopPainter(false); // remove as pointer
3845 
3846  var can3d = this.clear_3d_canvas(); // remove 3d canvas from main HTML element
3847 
3848  if (this._toolbar) this._toolbar.Cleanup(); // remove toolbar
3849 
3850  this.helpText();
3851 
3852  JSROOT.Painter.DisposeThreejsObject(this._scene);
3853 
3854  JSROOT.Painter.DisposeThreejsObject(this._full_geom);
3855 
3856  if (this._tcontrols)
3857  this._tcontrols.dispose();
3858 
3859  if (this._controls)
3860  this._controls.Cleanup();
3861 
3862  if (this._context_menu)
3863  this._renderer.domElement.removeEventListener( 'contextmenu', this._context_menu, false );
3864 
3865  if (this._datgui)
3866  this._datgui.destroy();
3867 
3868  if (this._worker) this._worker.terminate();
3869 
3870  delete this._animating;
3871 
3872  var obj = this.GetGeometry();
3873  if (obj && this.ctrl.is_main) {
3874  if (obj.$geo_painter===this) delete obj.$geo_painter; else
3875  if (obj.fVolume && obj.fVolume.$geo_painter===this) delete obj.fVolume.$geo_painter;
3876  }
3877 
3878  if (this._main_painter) {
3879  var pos = this._main_painter._slave_painters.indexOf(this);
3880  if (pos>=0) this._main_painter._slave_painters.splice(pos,1);
3881  }
3882 
3883  for (var k=0;k<this._slave_painters.length;++k) {
3884  var slave = this._slave_painters[k];
3885  if (slave && (slave._main_painter===this)) slave._main_painter = null;
3886  }
3887 
3888  delete this.geo_manager;
3889  delete this._highlight_handlers;
3890 
3891  JSROOT.TObjectPainter.prototype.Cleanup.call(this);
3892 
3893  delete this.ctrl;
3894  delete this.options;
3895 
3896  this.did_cleanup = true;
3897 
3898  if (can3d < 0) this.select_main().html("");
3899  }
3900 
3901  if (this._slave_painters)
3902  for (var k in this._slave_painters) {
3903  var slave = this._slave_painters[k];
3904  slave._main_painter = null;
3905  if (slave._clones === this._clones) slave._clones = null;
3906  }
3907 
3908  this._main_painter = null;
3909  this._slave_painters = [];
3910 
3911  if (this._renderer) {
3912  if (this._renderer.dispose) this._renderer.dispose();
3913  if (this._renderer.context) delete this._renderer.context;
3914  }
3915 
3916  delete this._scene;
3917  this._scene_width = 0;
3918  this._scene_height = 0;
3919  this._renderer = null;
3920  this._toplevel = null;
3921  delete this._full_geom;
3922  delete this._camera;
3923  delete this._camera0pos;
3924  delete this._lookat;
3925  delete this._selected_mesh;
3926 
3927  if (this._clones && this._clones_owner)
3928  this._clones.Cleanup(this._draw_nodes, this._build_shapes);
3929  delete this._clones;
3930  delete this._clones_owner;
3931  delete this._draw_nodes;
3932  delete this._drawing_ready;
3933  delete this._build_shapes;
3934  delete this._new_draw_nodes;
3935  delete this._new_append_nodes;
3936  delete this._last_camera_position;
3937 
3938  this.first_render_tm = 0; // time needed for first rendering
3939  this.last_render_tm = 0;
3940 
3941  this.drawing_stage = 0;
3942  delete this.drawing_log;
3943 
3944  delete this._datgui;
3945  delete this._controls;
3946  delete this._context_menu;
3947  delete this._tcontrols;
3948  delete this._toolbar;
3949 
3950  delete this._worker;
3951  }
3952 
3953  TGeoPainter.prototype.helpText = function(msg) {
3954  JSROOT.progress(msg);
3955  }
3956 
3957  TGeoPainter.prototype.CheckResize = function(arg) {
3958  var pad_painter = this.canv_painter();
3959 
3960  // firefox is the only browser which correctly supports resize of embedded canvas,
3961  // for others we should force canvas redrawing at every step
3962  if (pad_painter)
3963  if (!pad_painter.CheckCanvasResize(arg)) return false;
3964 
3965  var sz = this.size_for_3d();
3966 
3967  if ((this._scene_width === sz.width) && (this._scene_height === sz.height)) return false;
3968  if ((sz.width<10) || (sz.height<10)) return false;
3969 
3970  this._scene_width = sz.width;
3971  this._scene_height = sz.height;
3972 
3973  if (this._camera && this._renderer) {
3974  if (this._camera.type == "PerspectiveCamera")
3975  this._camera.aspect = this._scene_width / this._scene_height;
3976  this._camera.updateProjectionMatrix();
3977  this._renderer.setSize( this._scene_width, this._scene_height, !this._fit_main_area );
3978  if (this._effectComposer)
3979  this._effectComposer.setSize( this._scene_width, this._scene_height );
3980 
3981  if (!this.drawing_stage) this.Render3D();
3982  }
3983 
3984  return true;
3985  }
3986 
3987  TGeoPainter.prototype.toggleEnlarge = function() {
3988 
3989  if (d3.event) {
3990  d3.event.preventDefault();
3991  d3.event.stopPropagation();
3992  }
3993 
3994  if (this.enlarge_main('toggle'))
3995  this.CheckResize();
3996  }
3997 
3998 
3999  TGeoPainter.prototype.ownedByTransformControls = function(child) {
4000  var obj = child.parent;
4001  while (obj && !(obj instanceof THREE.TransformControls) ) {
4002  obj = obj.parent;
4003  }
4004  return (obj && (obj instanceof THREE.TransformControls));
4005  }
4006 
4007  TGeoPainter.prototype.accessObjectWireFrame = function(obj, on) {
4008  // either change mesh wireframe or return current value
4009  // return undefined when wireframe cannot be accessed
4010 
4011  if (!obj.hasOwnProperty("material") || (obj instanceof THREE.GridHelper)) return;
4012 
4013  if (this.ownedByTransformControls(obj)) return;
4014 
4015  if ((on !== undefined) && obj.stack)
4016  obj.material.wireframe = on;
4017 
4018  return obj.material.wireframe;
4019  }
4020 
4021  TGeoPainter.prototype.changedWireFrame = function() {
4022  if (!this._scene) return;
4023 
4024  var painter = this, on = this.ctrl.wireframe;
4025 
4026  this._scene.traverse(function(obj) { painter.accessObjectWireFrame(obj, on); });
4027 
4028  this.Render3D();
4029  }
4030 
4031  TGeoPainter.prototype.UpdateObject = function(obj) {
4032  if (obj === "same") return true;
4033  if (!obj || !obj._typename) return false;
4034  if (obj === this.GetObject()) return true;
4035 
4036  if (this.geo_manager && (obj._typename == "TGeoManager")) {
4037  this.geo_manager = obj;
4038  this.AssignObject({ _typename:"TGeoNode", fVolume: obj.fMasterVolume, fName: obj.fMasterVolume.fName, $geoh: obj.fMasterVolume.$geoh, _proxy: true });
4039  return true;
4040  }
4041 
4042  if (!this.MatchObjectType(obj._typename)) return false;
4043 
4044  this.AssignObject(obj);
4045  return true;
4046  }
4047 
4048  TGeoPainter.prototype.ClearDrawings = function() {
4049  if (this._clones && this._clones_owner)
4050  this._clones.Cleanup(this._draw_nodes, this._build_shapes);
4051  delete this._clones;
4052  delete this._clones_owner;
4053  delete this._draw_nodes;
4054  delete this._drawing_ready;
4055  delete this._build_shapes;
4056 
4057  delete this._extraObjects;
4058  delete this._clipCfg;
4059 
4060  JSROOT.Painter.DisposeThreejsObject(this._toplevel, true);
4061 
4062  this._full_redrawing = true;
4063  }
4064 
4065  TGeoPainter.prototype.RedrawObject = function(obj) {
4066  if (!this.UpdateObject(obj))
4067  return false;
4068 
4069  this.ClearDrawings();
4070 
4071  var draw_obj = this.GetGeometry(), name_prefix = "";
4072  if (this.geo_manager) name_prefix = draw_obj.fName;
4073 
4074  this.prepareObjectDraw(draw_obj, name_prefix);
4075 
4076  return true;
4077  }
4078 
4079  JSROOT.Painter.CreateGeoPainter = function(divid, obj, opt) {
4080  JSROOT.GEO.GradPerSegm = JSROOT.gStyle.GeoGradPerSegm;
4081  JSROOT.GEO.CompressComp = JSROOT.gStyle.GeoCompressComp;
4082 
4083  var painter = new TGeoPainter(obj);
4084 
4085  // one could use TGeoManager setting, but for some example JSROOT does not build composites
4086  // if (obj && obj._typename=='TGeoManager' && (obj.fNsegments > 3))
4087  // JSROOT.GEO.GradPerSegm = 360/obj.fNsegments;
4088 
4089  painter.SetDivId(divid, 5);
4090 
4091  painter._usesvg = JSROOT.Painter.UseSVGFor3D();
4092 
4093  painter._usesvgimg = !painter._usesvg && JSROOT.BatchMode;
4094 
4095  painter._webgl = !painter._usesvg && JSROOT.Painter.TestWebGL();
4096 
4097  painter.options = painter.decodeOptions(opt); // indicator of initialization
4098 
4099  // copy all attributes from options to control
4100  JSROOT.extend(painter.ctrl, painter.options);
4101 
4102  painter.ctrl.ssao.enabled = painter.options.usessao;
4103 
4104  // special handling for array of clips
4105  painter.ctrl.clip[0].enabled = painter.options.clipx;
4106  painter.ctrl.clip[1].enabled = painter.options.clipy;
4107  painter.ctrl.clip[2].enabled = painter.options.clipz;
4108 
4109  return painter;
4110  }
4111 
4112  JSROOT.Painter.drawGeoObject = function(divid, obj, opt) {
4113  if (!obj) return null;
4114 
4115  var shape = null, extras = null, extras_path = "", is_eve = false;
4116 
4117  if (('fShapeBits' in obj) && ('fShapeId' in obj)) {
4118  shape = obj; obj = null;
4119  } else if ((obj._typename === 'TGeoVolumeAssembly') || (obj._typename === 'TGeoVolume')) {
4120  shape = obj.fShape;
4121  } else if ((obj._typename === "TEveGeoShapeExtract") || (obj._typename === "ROOT::Experimental::TEveGeoShapeExtract")) {
4122  shape = obj.fShape; is_eve = true;
4123  } else if (obj._typename === 'TGeoManager') {
4124  shape = obj.fMasterVolume.fShape;
4125  } else if (obj._typename === 'TGeoOverlap') {
4126  extras = obj.fMarker; extras_path = "<prnt>/Marker";
4127  obj = JSROOT.GEO.buildOverlapVolume(obj);
4128  if (!opt) opt = "wire";
4129  } else if ('fVolume' in obj) {
4130  if (obj.fVolume) shape = obj.fVolume.fShape;
4131  } else {
4132  obj = null;
4133  }
4134 
4135  if ((typeof opt == "string") && opt.indexOf("comp")==0 && shape && (shape._typename == 'TGeoCompositeShape') && shape.fNode) {
4136  var maxlvl = 1;
4137  opt = opt.substr(4);
4138  if (opt[0] == "x") { maxlvl = 999; opt = opt.substr(1) + "_vislvl999"; }
4139  obj = JSROOT.GEO.buildCompositeVolume(shape, maxlvl);
4140  }
4141 
4142  if (!obj && shape)
4143  obj = JSROOT.extend(JSROOT.Create("TEveGeoShapeExtract"),
4144  { fTrans: null, fShape: shape, fRGBA: [0, 1, 0, 1], fElements: null, fRnrSelf: true });
4145 
4146  if (!obj) return null;
4147 
4148  var painter = JSROOT.Painter.CreateGeoPainter(divid, obj, opt);
4149 
4150  if (painter.ctrl.is_main && !obj.$geo_painter)
4151  obj.$geo_painter = painter;
4152 
4153  if (!painter.ctrl.is_main && painter.ctrl.project && obj.$geo_painter) {
4154  painter._main_painter = obj.$geo_painter;
4155  painter._main_painter._slave_painters.push(painter);
4156  }
4157 
4158  if (is_eve && !painter.ctrl.vislevel || (painter.ctrl.vislevel < 9))
4159  painter.ctrl.vislevel = 9;
4160 
4161  if (extras) {
4162  painter._splitColors = true;
4163  painter.addExtra(extras, extras_path);
4164  }
4165 
4166  // this.ctrl.script_name = 'https://root.cern/js/files/geom/geomAlice.C'
4167 
4168  painter.checkScript(painter.ctrl.script_name, painter.prepareObjectDraw.bind(painter));
4169 
4170  return painter;
4171  }
4172 
4174  JSROOT.Painter.drawGeometry = JSROOT.Painter.drawGeoObject;
4175 
4176  // ===============================================================================
4177 
4180  JSROOT.GEO.buildCompositeVolume = function(comp, maxlvl, side) {
4181 
4182  if (maxlvl === undefined) maxlvl = 1;
4183  if (!side) {
4184  this.$comp_col_cnt = 0;
4185  side = "";
4186  }
4187 
4188  var vol = JSROOT.Create("TGeoVolume");
4189  JSROOT.GEO.SetBit(vol, JSROOT.GEO.BITS.kVisThis, true);
4190  JSROOT.GEO.SetBit(vol, JSROOT.GEO.BITS.kVisDaughters, true);
4191 
4192  if ((side && (comp._typename!=='TGeoCompositeShape')) || (maxlvl<=0)) {
4193  vol.fName = side;
4194  vol.fLineColor = (this.$comp_col_cnt++ % 8) + 2;
4195  vol.fShape = comp;
4196  return vol;
4197  }
4198 
4199  if (side) side += "/";
4200  vol.$geoh = true; // workaround, let know browser that we are in volumes hierarchy
4201  vol.fName = "";
4202 
4203  var node1 = JSROOT.Create("TGeoNodeMatrix");
4204  JSROOT.GEO.SetBit(node1, JSROOT.GEO.BITS.kVisThis, true);
4205  JSROOT.GEO.SetBit(node1, JSROOT.GEO.BITS.kVisDaughters, true);
4206  node1.fName = "Left";
4207  node1.fMatrix = comp.fNode.fLeftMat;
4208  node1.fVolume = JSROOT.GEO.buildCompositeVolume(comp.fNode.fLeft, maxlvl-1, side + "Left");
4209 
4210  var node2 = JSROOT.Create("TGeoNodeMatrix");
4211  JSROOT.GEO.SetBit(node2, JSROOT.GEO.BITS.kVisThis, true);
4212  JSROOT.GEO.SetBit(node2, JSROOT.GEO.BITS.kVisDaughters, true);
4213  node2.fName = "Right";
4214  node2.fMatrix = comp.fNode.fRightMat;
4215  node2.fVolume = JSROOT.GEO.buildCompositeVolume(comp.fNode.fRight, maxlvl-1, side + "Right");
4216 
4217  vol.fNodes = JSROOT.Create("TList");
4218  vol.fNodes.Add(node1);
4219  vol.fNodes.Add(node2);
4220 
4221  if (!side) delete this.$comp_col_cnt;
4222 
4223  return vol;
4224  }
4225 
4228  JSROOT.GEO.buildOverlapVolume = function(overlap) {
4229 
4230  var vol = JSROOT.Create("TGeoVolume");
4231 
4232  JSROOT.GEO.SetBit(vol, JSROOT.GEO.BITS.kVisDaughters, true);
4233  vol.$geoh = true; // workaround, let know browser that we are in volumes hierarchy
4234  vol.fName = "";
4235 
4236  var node1 = JSROOT.Create("TGeoNodeMatrix");
4237  node1.fName = overlap.fVolume1.fName || "Overlap1";
4238  node1.fMatrix = overlap.fMatrix1;
4239  node1.fVolume = overlap.fVolume1;
4240  // node1.fVolume.fLineColor = 2; // color assigned with _splitColors
4241 
4242  var node2 = JSROOT.Create("TGeoNodeMatrix");
4243  node2.fName = overlap.fVolume2.fName || "Overlap2";
4244  node2.fMatrix = overlap.fMatrix2;
4245  node2.fVolume = overlap.fVolume2;
4246  // node2.fVolume.fLineColor = 3; // color assigned with _splitColors
4247 
4248  vol.fNodes = JSROOT.Create("TList");
4249  vol.fNodes.Add(node1);
4250  vol.fNodes.Add(node2);
4251 
4252  return vol;
4253  }
4254 
4255  JSROOT.GEO.provideVisStyle = function(obj) {
4256  if ((obj._typename === 'TEveGeoShapeExtract') || (obj._typename === 'ROOT::Experimental::TEveGeoShapeExtract'))
4257  return obj.fRnrSelf ? " geovis_this" : "";
4258 
4259  var vis = !JSROOT.GEO.TestBit(obj, JSROOT.GEO.BITS.kVisNone) &&
4260  JSROOT.GEO.TestBit(obj, JSROOT.GEO.BITS.kVisThis),
4261  chld = JSROOT.GEO.TestBit(obj, JSROOT.GEO.BITS.kVisDaughters);
4262 
4263  if (chld && (!obj.fNodes || (obj.fNodes.arr.length === 0))) chld = false;
4264 
4265  if (vis && chld) return " geovis_all";
4266  if (vis) return " geovis_this";
4267  if (chld) return " geovis_daughters";
4268  return "";
4269  }
4270 
4271 
4272  JSROOT.GEO.getBrowserItem = function(item, itemname, callback) {
4273  // mark object as belong to the hierarchy, require to
4274  if (item._geoobj) item._geoobj.$geoh = true;
4275 
4276  JSROOT.CallBack(callback, item, item._geoobj);
4277  }
4278 
4279 
4280  JSROOT.GEO.createItem = function(node, obj, name) {
4281  var sub = {
4282  _kind: "ROOT." + obj._typename,
4283  _name: name ? name : JSROOT.GEO.ObjectName(obj),
4284  _title: obj.fTitle,
4285  _parent: node,
4286  _geoobj: obj,
4287  _get: JSROOT.GEO.getBrowserItem
4288  };
4289 
4290  var volume, shape, subnodes, iseve = false;
4291 
4292  if (obj._typename == "TGeoMaterial") sub._icon = "img_geomaterial"; else
4293  if (obj._typename == "TGeoMedium") sub._icon = "img_geomedium"; else
4294  if (obj._typename == "TGeoMixture") sub._icon = "img_geomixture"; else
4295  if ((obj._typename.indexOf("TGeoNode")===0) && obj.fVolume) {
4296  sub._title = "node:" + obj._typename;
4297  if (obj.fTitle.length > 0) sub._title += " " + obj.fTitle;
4298  volume = obj.fVolume;
4299  } else
4300  if (obj._typename.indexOf("TGeoVolume")===0) {
4301  volume = obj;
4302  } else
4303  if ((obj._typename == "TEveGeoShapeExtract") || (obj._typename == "ROOT::Experimental::TEveGeoShapeExtract") ) {
4304  iseve = true;
4305  shape = obj.fShape;
4306  subnodes = obj.fElements ? obj.fElements.arr : null;
4307  } else
4308  if ((obj.fShapeBits !== undefined) && (obj.fShapeId !== undefined)) {
4309  shape = obj;
4310  }
4311 
4312  if (volume) {
4313  shape = volume.fShape;
4314  subnodes = volume.fNodes ? volume.fNodes.arr : null;
4315  }
4316 
4317  if (volume || shape || subnodes) {
4318  if (volume) sub._volume = volume;
4319 
4320  if (subnodes) {
4321  sub._more = true;
4322  sub._expand = JSROOT.GEO.expandObject;
4323  } else
4324  if (shape && (shape._typename === "TGeoCompositeShape") && shape.fNode) {
4325  sub._more = true;
4326  sub._shape = shape;
4327  sub._expand = function(node, obj) {
4328  JSROOT.GEO.createItem(node, node._shape.fNode.fLeft, 'Left');
4329  JSROOT.GEO.createItem(node, node._shape.fNode.fRight, 'Right');
4330  return true;
4331  }
4332  }
4333 
4334  if (!sub._title && (obj._typename != "TGeoVolume")) sub._title = obj._typename;
4335 
4336  if (shape) {
4337  if (sub._title == "")
4338  sub._title = shape._typename;
4339 
4340  sub._icon = JSROOT.GEO.getShapeIcon(shape);
4341  } else {
4342  sub._icon = sub._more ? "img_geocombi" : "img_geobbox";
4343  }
4344 
4345  if (volume)
4346  sub._icon += JSROOT.GEO.provideVisStyle(volume);
4347  else if (iseve)
4348  sub._icon += JSROOT.GEO.provideVisStyle(obj);
4349 
4350  sub._menu = JSROOT.GEO.provideMenu;
4351  sub._icon_click = JSROOT.GEO.browserIconClick;
4352  }
4353 
4354  if (!node._childs) node._childs = [];
4355 
4356  if (!sub._name)
4357  if (typeof node._name === 'string') {
4358  sub._name = node._name;
4359  if (sub._name.lastIndexOf("s")===sub._name.length-1)
4360  sub._name = sub._name.substr(0, sub._name.length-1);
4361  sub._name += "_" + node._childs.length;
4362  } else {
4363  sub._name = "item_" + node._childs.length;
4364  }
4365 
4366  node._childs.push(sub);
4367 
4368  return sub;
4369  }
4370 
4371  JSROOT.GEO.createList = function(parent, lst, name, title) {
4372 
4373  if (!lst || !('arr' in lst) || (lst.arr.length==0)) return;
4374 
4375  var item = {
4376  _name: name,
4377  _kind: "ROOT.TList",
4378  _title: title,
4379  _more: true,
4380  _geoobj: lst,
4381  _parent: parent,
4382  }
4383 
4384  item._get = function(item, itemname, callback) {
4385  if ('_geoobj' in item)
4386  return JSROOT.CallBack(callback, item, item._geoobj);
4387 
4388  JSROOT.CallBack(callback, item, null);
4389  }
4390 
4391  item._expand = function(node, lst) {
4392  // only childs
4393 
4394  if ('fVolume' in lst)
4395  lst = lst.fVolume.fNodes;
4396 
4397  if (!('arr' in lst)) return false;
4398 
4399  node._childs = [];
4400 
4401  JSROOT.GEO.CheckDuplicates(null, lst.arr);
4402 
4403  for (var n in lst.arr)
4404  JSROOT.GEO.createItem(node, lst.arr[n]);
4405 
4406  return true;
4407  }
4408 
4409  if (!parent._childs) parent._childs = [];
4410  parent._childs.push(item);
4411 
4412  };
4413 
4414  JSROOT.GEO.provideMenu = function(menu, item, hpainter) {
4415 
4416  if (!item._geoobj) return false;
4417 
4418  var obj = item._geoobj, vol = item._volume,
4419  iseve = ((obj._typename === 'TEveGeoShapeExtract') || (obj._typename === 'ROOT::Experimental::TEveGeoShapeExtract'));
4420 
4421  if (!vol && !iseve) return false;
4422 
4423  menu.add("separator");
4424 
4425  function ScanEveVisible(obj, arg, skip_this) {
4426  if (!arg) arg = { visible: 0, hidden: 0 };
4427 
4428  if (!skip_this) {
4429  if (arg.assign!==undefined) obj.fRnrSelf = arg.assign; else
4430  if (obj.fRnrSelf) arg.vis++; else arg.hidden++;
4431  }
4432 
4433  if (obj.fElements)
4434  for (var n=0;n<obj.fElements.arr.length;++n)
4435  ScanEveVisible(obj.fElements.arr[n], arg, false);
4436 
4437  return arg;
4438  }
4439 
4440  function ToggleEveVisibility(arg) {
4441  if (arg === 'self') {
4442  obj.fRnrSelf = !obj.fRnrSelf;
4443  item._icon = item._icon.split(" ")[0] + JSROOT.GEO.provideVisStyle(obj);
4444  hpainter.UpdateTreeNode(item);
4445  } else {
4446  ScanEveVisible(obj, { assign: (arg === "true") }, true);
4447  hpainter.ForEach(function(m) {
4448  // update all child items
4449  if (m._geoobj && m._icon) {
4450  m._icon = item._icon.split(" ")[0] + JSROOT.GEO.provideVisStyle(m._geoobj);
4451  hpainter.UpdateTreeNode(m);
4452  }
4453  }, item);
4454  }
4455 
4456  JSROOT.GEO.findItemWithPainter(item, 'testGeomChanges');
4457  }
4458 
4459  function ToggleMenuBit(arg) {
4460  JSROOT.GEO.ToggleBit(vol, arg);
4461  var newname = item._icon.split(" ")[0] + JSROOT.GEO.provideVisStyle(vol);
4462  hpainter.ForEach(function(m) {
4463  // update all items with that volume
4464  if (item._volume === m._volume) {
4465  m._icon = newname;
4466  hpainter.UpdateTreeNode(m);
4467  }
4468  });
4469 
4470  hpainter.UpdateTreeNode(item);
4471  JSROOT.GEO.findItemWithPainter(item, 'testGeomChanges');
4472  }
4473 
4474  if ((item._geoobj._typename.indexOf("TGeoNode")===0) && JSROOT.GEO.findItemWithPainter(item))
4475  menu.add("Focus", function() {
4476 
4477  var drawitem = JSROOT.GEO.findItemWithPainter(item);
4478 
4479  if (!drawitem) return;
4480 
4481  var fullname = hpainter.itemFullName(item, drawitem);
4482 
4483  if (drawitem._painter && typeof drawitem._painter.focusOnItem == 'function')
4484  drawitem._painter.focusOnItem(fullname);
4485  });
4486 
4487  if (iseve) {
4488  menu.addchk(obj.fRnrSelf, "Visible", "self", ToggleEveVisibility);
4489  var res = ScanEveVisible(obj, undefined, true);
4490 
4491  if (res.hidden + res.visible > 0)
4492  menu.addchk((res.hidden==0), "Daughters", (res.hidden!=0) ? "true" : "false", ToggleEveVisibility);
4493 
4494  } else {
4495  menu.addchk(JSROOT.GEO.TestBit(vol, JSROOT.GEO.BITS.kVisNone), "Invisible",
4496  JSROOT.GEO.BITS.kVisNone, ToggleMenuBit);
4497  menu.addchk(JSROOT.GEO.TestBit(vol, JSROOT.GEO.BITS.kVisThis), "Visible",
4498  JSROOT.GEO.BITS.kVisThis, ToggleMenuBit);
4499  menu.addchk(JSROOT.GEO.TestBit(vol, JSROOT.GEO.BITS.kVisDaughters), "Daughters",
4500  JSROOT.GEO.BITS.kVisDaughters, ToggleMenuBit);
4501  }
4502 
4503  return true;
4504  }
4505 
4506  JSROOT.GEO.findItemWithPainter = function(hitem, funcname) {
4507  while (hitem) {
4508  if (hitem._painter && hitem._painter._camera) {
4509  if (funcname && typeof hitem._painter[funcname] == 'function')
4510  hitem._painter[funcname]();
4511  return hitem;
4512  }
4513  hitem = hitem._parent;
4514  }
4515  return null;
4516  }
4517 
4518  JSROOT.GEO.updateBrowserIcons = function(obj, hpainter) {
4519  if (!obj || !hpainter) return;
4520 
4521  hpainter.ForEach(function(m) {
4522  // update all items with that volume
4523  if ((obj === m._volume) || (obj === m._geoobj)) {
4524  m._icon = m._icon.split(" ")[0] + JSROOT.GEO.provideVisStyle(obj);
4525  hpainter.UpdateTreeNode(m);
4526  }
4527  });
4528  }
4529 
4530  JSROOT.GEO.browserIconClick = function(hitem, hpainter) {
4531  if (hitem._volume) {
4532  if (hitem._more && hitem._volume.fNodes && (hitem._volume.fNodes.arr.length>0))
4533  JSROOT.GEO.ToggleBit(hitem._volume, JSROOT.GEO.BITS.kVisDaughters);
4534  else
4535  JSROOT.GEO.ToggleBit(hitem._volume, JSROOT.GEO.BITS.kVisThis);
4536 
4537  JSROOT.GEO.updateBrowserIcons(hitem._volume, hpainter);
4538 
4539  JSROOT.GEO.findItemWithPainter(hitem, 'testGeomChanges');
4540  return false; // no need to update icon - we did it ourself
4541  }
4542 
4543  if (hitem._geoobj && (( hitem._geoobj._typename == "TEveGeoShapeExtract") || ( hitem._geoobj._typename == "ROOT::Experimental::TEveGeoShapeExtract"))) {
4544  hitem._geoobj.fRnrSelf = !hitem._geoobj.fRnrSelf;
4545 
4546  JSROOT.GEO.updateBrowserIcons(hitem._geoobj, hpainter);
4547  JSROOT.GEO.findItemWithPainter(hitem, 'testGeomChanges');
4548  return false; // no need to update icon - we did it ourself
4549  }
4550 
4551 
4552  // first check that geo painter assigned with the item
4553  var drawitem = JSROOT.GEO.findItemWithPainter(hitem);
4554  if (!drawitem) return false;
4555 
4556  var newstate = drawitem._painter.ExtraObjectVisible(hpainter, hitem, true);
4557 
4558  // return true means browser should update icon for the item
4559  return (newstate!==undefined) ? true : false;
4560  }
4561 
4562  JSROOT.GEO.getShapeIcon = function(shape) {
4563  switch (shape._typename) {
4564  case "TGeoArb8" : return "img_geoarb8"; break;
4565  case "TGeoCone" : return "img_geocone"; break;
4566  case "TGeoConeSeg" : return "img_geoconeseg"; break;
4567  case "TGeoCompositeShape" : return "img_geocomposite"; break;
4568  case "TGeoTube" : return "img_geotube"; break;
4569  case "TGeoTubeSeg" : return "img_geotubeseg"; break;
4570  case "TGeoPara" : return "img_geopara"; break;
4571  case "TGeoParaboloid" : return "img_geoparab"; break;
4572  case "TGeoPcon" : return "img_geopcon"; break;
4573  case "TGeoPgon" : return "img_geopgon"; break;
4574  case "TGeoShapeAssembly" : return "img_geoassembly"; break;
4575  case "TGeoSphere" : return "img_geosphere"; break;
4576  case "TGeoTorus" : return "img_geotorus"; break;
4577  case "TGeoTrd1" : return "img_geotrd1"; break;
4578  case "TGeoTrd2" : return "img_geotrd2"; break;
4579  case "TGeoXtru" : return "img_geoxtru"; break;
4580  case "TGeoTrap" : return "img_geotrap"; break;
4581  case "TGeoGtra" : return "img_geogtra"; break;
4582  case "TGeoEltu" : return "img_geoeltu"; break;
4583  case "TGeoHype" : return "img_geohype"; break;
4584  case "TGeoCtub" : return "img_geoctub"; break;
4585  }
4586  return "img_geotube";
4587  }
4588 
4589  JSROOT.GEO.getBrowserIcon = function(hitem, hpainter) {
4590  var icon = "";
4591  if (hitem._kind == 'ROOT.TEveTrack') icon = 'img_evetrack'; else
4592  if (hitem._kind == 'ROOT.TEvePointSet') icon = 'img_evepoints'; else
4593  if (hitem._kind == 'ROOT.TPolyMarker3D') icon = 'img_evepoints';
4594  if (icon.length>0) {
4595  var drawitem = JSROOT.GEO.findItemWithPainter(hitem);
4596  if (drawitem)
4597  if (drawitem._painter.ExtraObjectVisible(hpainter, hitem))
4598  icon += " geovis_this";
4599  }
4600  return icon;
4601  }
4602 
4603  JSROOT.GEO.expandObject = function(parent, obj) {
4604  if (!parent || !obj) return false;
4605 
4606  var isnode = (obj._typename.indexOf('TGeoNode') === 0),
4607  isvolume = (obj._typename.indexOf('TGeoVolume') === 0),
4608  ismanager = (obj._typename === 'TGeoManager'),
4609  iseve = ((obj._typename === 'TEveGeoShapeExtract') || (obj._typename === 'ROOT::Experimental::TEveGeoShapeExtract')),
4610  isoverlap = (obj._typename === 'TGeoOverlap');
4611 
4612  if (!isnode && !isvolume && !ismanager && !iseve && !isoverlap) return false;
4613 
4614  if (parent._childs) return true;
4615 
4616  if (ismanager) {
4617  JSROOT.GEO.createList(parent, obj.fMaterials, "Materials", "list of materials");
4618  JSROOT.GEO.createList(parent, obj.fMedia, "Media", "list of media");
4619  JSROOT.GEO.createList(parent, obj.fTracks, "Tracks", "list of tracks");
4620  JSROOT.GEO.createList(parent, obj.fOverlaps, "Overlaps", "list of detected overlaps");
4621  JSROOT.GEO.createItem(parent, obj.fMasterVolume);
4622  return true;
4623  }
4624 
4625  if (isoverlap) {
4626  JSROOT.GEO.createItem(parent, obj.fVolume1);
4627  JSROOT.GEO.createItem(parent, obj.fVolume2);
4628  JSROOT.GEO.createItem(parent, obj.fMarker, 'Marker');
4629  return true;
4630  }
4631 
4632  var volume, subnodes, shape;
4633 
4634  if (iseve) {
4635  subnodes = obj.fElements ? obj.fElements.arr : null;
4636  shape = obj.fShape;
4637  } else {
4638  volume = (isnode ? obj.fVolume : obj);
4639  subnodes = volume && volume.fNodes ? volume.fNodes.arr : null;
4640  shape = volume ? volume.fShape : null;
4641  }
4642 
4643  if (!subnodes && shape && (shape._typename === "TGeoCompositeShape") && shape.fNode) {
4644  if (!parent._childs) {
4645  JSROOT.GEO.createItem(parent, shape.fNode.fLeft, 'Left');
4646  JSROOT.GEO.createItem(parent, shape.fNode.fRight, 'Right');
4647  }
4648 
4649  return true;
4650  }
4651 
4652  if (!subnodes) return false;
4653 
4654  JSROOT.GEO.CheckDuplicates(obj, subnodes);
4655 
4656  for (var i=0;i<subnodes.length;++i)
4657  JSROOT.GEO.createItem(parent, subnodes[i]);
4658 
4659  return true;
4660  }
4661 
4662  JSROOT.addDrawFunc({ name: "TGeoVolumeAssembly", icon: 'img_geoassembly', func: JSROOT.Painter.drawGeoObject, expand: JSROOT.GEO.expandObject, opt: ";more;all;count" });
4663  JSROOT.addDrawFunc({ name: "TEvePointSet", icon_get: JSROOT.GEO.getBrowserIcon, icon_click: JSROOT.GEO.browserIconClick });
4664  JSROOT.addDrawFunc({ name: "TEveTrack", icon_get: JSROOT.GEO.getBrowserIcon, icon_click: JSROOT.GEO.browserIconClick });
4665 
4666  JSROOT.TGeoPainter = TGeoPainter;
4667 
4668  JSROOT.Painter.GeoDrawingControl = GeoDrawingControl;
4669 
4670  return JSROOT.Painter;
4671 
4672 }));