otsdaq_utilities  v2_05_02_indev
JSRoot3DPainter.js
1 
4 (function( factory ) {
5  if ( typeof define === "function" && define.amd ) {
6  define( ['JSRootPainter', 'd3', 'threejs', 'threejs_all'], factory );
7  } else if (typeof exports === 'object' && typeof module !== 'undefined') {
8  var jsroot = require("./JSRootCore.js");
9  factory(jsroot, require("d3"), require("three"), require("./three.extra.min.js"),
10  jsroot.nodejs || (typeof document=='undefined') ? jsroot.nodejs_document : document);
11  } else {
12  if (typeof JSROOT == 'undefined')
13  throw new Error('JSROOT is not defined', 'JSRoot3DPainter.js');
14  if (typeof d3 != 'object')
15  throw new Error('This extension requires d3.js', 'JSRoot3DPainter.js');
16  if (typeof THREE == 'undefined')
17  throw new Error('THREE is not defined', 'JSRoot3DPainter.js');
18  factory(JSROOT, d3, THREE);
19  }
20 } (function(JSROOT, d3, THREE, THREE_MORE, document) {
21 
22  "use strict";
23 
24  JSROOT.sources.push("3d");
25 
26  if ((typeof document=='undefined') && (typeof window=='object')) document = window.document;
27 
28  if (typeof JSROOT.Painter != 'object')
29  throw new Error('JSROOT.Painter is not defined', 'JSRoot3DPainter.js');
30 
39  JSROOT.Painter.TestWebGL = function() {
40 
41  if (JSROOT.gStyle.NoWebGL || JSROOT.nodejs) return false;
42 
43  if ('_Detect_WebGL' in this) return this._Detect_WebGL;
44 
45  try {
46  var canvas = document.createElement( 'canvas' );
47  this._Detect_WebGL = !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) );
48  //res = !!window.WebGLRenderingContext && !!document.createElement('canvas').getContext('experimental-webgl');
49  } catch (e) {
50  this._Detect_WebGL = false;
51  }
52 
53  return this._Detect_WebGL;
54  }
55 
63  JSROOT.Painter.UseSVGFor3D = function() {
64  if (!JSROOT.nodejs) return false;
65 
66  if (this._Detect_UseSVGFor3D !== undefined)
67  return this._Detect_UseSVGFor3D;
68 
69  var nodejs_canvas = null;
70 
71  try {
72  nodejs_canvas = require('canvas');
73  } catch (er) {
74  nodejs_canvas = null;
75  }
76 
77  this._Detect_UseSVGFor3D = !nodejs_canvas;
78  return this._Detect_UseSVGFor3D;
79  }
80 
91  JSROOT.Painter.Create3DRenderer = function(width, height, usesvg, makeimage, usewebgl, args) {
92  var res = {
93  renderer: null,
94  dom: null,
95  usesvg: usesvg,
96  usesvgimg: usesvg && JSROOT.gStyle.ImageSVG
97  }
98 
99  if (!args) args = { antialias: true, alpha: true };
100 
101  if (JSROOT.nodejs) {
102  res.usewebgl = false;
103  } else if (usewebgl !== undefined) {
104  res.usewebgl = usewebgl;
105  } else {
106  res.usewebgl = JSROOT.Painter.TestWebGL();
107  }
108 
109  // solves problem with toDataUrl in headless mode of chrome
110  // found https://stackoverflow.com/questions/48011613
111  if (JSROOT.BatchMode && JSROOT.browser.isChromeHeadless && res.usewebgl)
112  args.premultipliedAlpha = false;
113 
114  if (usesvg) {
115 
116  var nodejs_canvas = null;
117 
118  if (JSROOT.nodejs && res.usesvgimg) {
119  try {
120  nodejs_canvas = require('canvas');
121  } catch (er) {
122  nodejs_canvas = null;
123  res.usesvgimg = false;
124  JSROOT.gStyle.ImageSVG = false; // no need to try once again
125  }
126  }
127 
128  if (res.usesvgimg) {
129 
130  if (nodejs_canvas) {
131  args.canvas = new nodejs_canvas(width, height);
132  args.canvas.style = {};
133  }
134 
135  res.renderer = res.usewebgl ? new THREE.WebGLRenderer(args) : new THREE.SoftwareRenderer(args);
136  } else {
137  // this.renderer = new THREE.SVGRenderer({ precision: 0, astext: true });
138  res.renderer = THREE.CreateSVGRenderer(false, 0, document);
139  }
140 
141  if (res.usesvgimg || (res.renderer.makeOuterHTML !== undefined)) {
142  // this is indication of new three.js functionality
143  if (!JSROOT.svg_workaround) JSROOT.svg_workaround = [];
144  res.renderer.workaround_id = JSROOT.svg_workaround.length;
145  JSROOT.svg_workaround[res.renderer.workaround_id] = "<svg></svg>"; // dummy, need to be replaced
146 
147  // replace DOM element in renderer
148  res.dom = document.createElementNS( 'http://www.w3.org/2000/svg', 'path');
149  res.dom.setAttribute('jsroot_svg_workaround', res.renderer.workaround_id);
150  }
151  } else {
152  res.renderer = res.usewebgl ? new THREE.WebGLRenderer(args) : new THREE.SoftwareRenderer(args);
153  }
154 
155  // res.renderer.setClearColor("#000000", 1);
156  // res.renderer.setClearColor(0x0, 0);
157  res.renderer.setSize(width, height);
158 
159  if (!res.dom) {
160  res.dom = res.renderer.domElement;
161  if (!usesvg && makeimage) {
162  res.dom = res.renderer.svgImage = document.createElementNS('http://www.w3.org/2000/svg','image');
163  d3.select(res.dom).attr("width", width)
164  .attr("height", height);
165  }
166  }
167 
168  return res;
169  }
170 
171  JSROOT.Painter.AfterRender3D = function(renderer) {
172  if (renderer.svgImage) {
173  var dataUrl = renderer.domElement.toDataURL("image/png");
174  var attrname = JSROOT.nodejs ? "xlink_href_nodejs" : "xlink:href";
175  d3.select(renderer.svgImage).attr(attrname, dataUrl);
176  }
177 
178  // when using SVGrenderer producing text output, provide result
179  if (renderer.workaround_id !== undefined) {
180  if (typeof renderer.makeOuterHTML == 'function') {
181  JSROOT.svg_workaround[renderer.workaround_id] = renderer.makeOuterHTML();
182  } else {
183  var canvas = renderer.domElement;
184  var dataUrl = canvas.toDataURL("image/png");
185  var svg = '<image width="' + canvas.width + '" height="' + canvas.height + '" xlink:href="' + dataUrl + '"></image>';
186  JSROOT.svg_workaround[renderer.workaround_id] = svg;
187  }
188  }
189  }
190 
191  JSROOT.Painter.ProcessSVGWorkarounds = function(svg) {
192  if (!JSROOT.svg_workaround) return svg;
193  for (var k=0;k<JSROOT.svg_workaround.length;++k)
194  svg = svg.replace('<path jsroot_svg_workaround="' + k + '"></path>', JSROOT.svg_workaround[k]);
195  JSROOT.svg_workaround = undefined;
196  return svg;
197  }
198 
199 
200  JSROOT.Painter.TooltipFor3D = function(prnt, canvas) {
201  this.tt = null;
202  this.cont = null;
203  this.lastlbl = '';
204  this.parent = prnt ? prnt : document.body;
205  this.canvas = canvas; // we need canvas to recalculate mouse events
206  this.abspos = !prnt;
207 
208  this.check_parent = function(prnt) {
209  if (prnt && (this.parent !== prnt)) {
210  this.hide();
211  this.parent = prnt;
212  }
213  }
214 
215  // extract position from event - can be used to process it later when event is gone
216  this.extract_pos = function(e) {
217  if (typeof e == 'object' && (e.u !== undefined) && (e.l !== undefined)) return e;
218  var res = { u: 0, l: 0 };
219  if (this.abspos) {
220  res.l = JSROOT.browser.isIE ? (e.clientX + document.documentElement.scrollLeft) : e.pageX;
221  res.u = JSROOT.browser.isIE ? (e.clientY + document.documentElement.scrollTop) : e.pageY;
222  } else {
223  res.l = e.offsetX;
224  res.u = e.offsetY;
225  }
226 
227  return res;
228  }
229 
230  // method used to define position of next tooltip
231  // event is delivered from canvas,
232  // but position should be calculated relative to the element where tooltip is placed
233 
234  this.pos = function(e) {
235 
236  if (!this.tt) return;
237 
238  var pos = this.extract_pos(e);
239  if (!this.abspos) {
240  var rect1 = this.parent.getBoundingClientRect(),
241  rect2 = this.canvas.getBoundingClientRect();
242 
243  if ((rect1.left !== undefined) && (rect2.left!== undefined)) pos.l += (rect2.left-rect1.left);
244 
245  if ((rect1.top !== undefined) && (rect2.top!== undefined)) pos.u += rect2.top-rect1.top;
246 
247  if (pos.l + this.tt.offsetWidth + 3 >= this.parent.offsetWidth)
248  pos.l = this.parent.offsetWidth - this.tt.offsetWidth - 3;
249 
250  if (pos.u + this.tt.offsetHeight + 15 >= this.parent.offsetHeight)
251  pos.u = this.parent.offsetHeight - this.tt.offsetHeight - 15;
252 
253  // one should find parent with non-static position,
254  // all absolute coordinates calculated relative to such node
255  var abs_parent = this.parent;
256  while (abs_parent) {
257  var style = getComputedStyle(abs_parent);
258  if (!style || (style.position !== 'static')) break;
259  if (!abs_parent.parentNode || (abs_parent.parentNode.nodeType != 1)) break;
260  abs_parent = abs_parent.parentNode;
261  }
262 
263  if (abs_parent && (abs_parent !== this.parent)) {
264  var rect0 = abs_parent.getBoundingClientRect();
265  pos.l += (rect1.left - rect0.left);
266  pos.u += (rect1.top - rect0.top);
267  }
268  }
269 
270  this.tt.style.top = (pos.u + 15) + 'px';
271  this.tt.style.left = (pos.l + 3) + 'px';
272  };
273 
274  this.show = function(v, mouse_pos, status_func) {
275  // if (JSROOT.gStyle.Tooltip <= 0) return;
276  if (!v || (v==="")) return this.hide();
277 
278  if (v && (typeof v =='object') && (v.lines || v.line)) {
279  if (v.only_status) return this.hide();
280 
281  if (v.line) {
282  v = v.line;
283  } else {
284  var res = v.lines[0];
285  for (var n=1;n<v.lines.length;++n) res+= "<br/>" + v.lines[n];
286  v = res;
287  }
288  }
289 
290  if (this.tt === null) {
291  this.tt = document.createElement('div');
292  this.tt.setAttribute('class', 'jsroot_tt3d_main');
293  this.cont = document.createElement('div');
294  this.cont.setAttribute('class', 'jsroot_tt3d_cont');
295  this.tt.appendChild(this.cont);
296  this.parent.appendChild(this.tt);
297  }
298 
299  if (this.lastlbl !== v) {
300  this.cont.innerHTML = v;
301  this.lastlbl = v;
302  this.tt.style.width = 'auto'; // let it be automatically resizing...
303  if (JSROOT.browser.isIE)
304  this.tt.style.width = this.tt.offsetWidth;
305  }
306  };
307 
308  this.hide = function() {
309  if (this.tt !== null)
310  this.parent.removeChild(this.tt);
311 
312  this.tt = null;
313  this.lastlbl = "";
314  }
315 
316  return this;
317  }
318 
319 
321  JSROOT.Painter.CreateOrbitControl = function(painter, camera, scene, renderer, lookat) {
322 
323  if (JSROOT.gStyle.Zooming && JSROOT.gStyle.ZoomWheel)
324  renderer.domElement.addEventListener( 'wheel', control_mousewheel);
325 
326  var enable_zoom = JSROOT.gStyle.Zooming && JSROOT.gStyle.ZoomMouse,
327  enable_select = typeof painter.ProcessMouseClick == "function";
328 
329  if (enable_zoom || enable_select) {
330  renderer.domElement.addEventListener( 'mousedown', control_mousedown);
331  renderer.domElement.addEventListener( 'mouseup', control_mouseup);
332  }
333 
334  var control = new THREE.OrbitControls(camera, renderer.domElement);
335 
336  control.enableDamping = false;
337  control.dampingFactor = 1.0;
338  control.enableZoom = true;
339  if (lookat) {
340  control.target.copy(lookat);
341  control.target0.copy(lookat);
342  control.update();
343  }
344 
345  control.tooltip = new JSROOT.Painter.TooltipFor3D(painter.select_main().node(), renderer.domElement);
346 
347  control.painter = painter;
348  control.camera = camera;
349  control.scene = scene;
350  control.renderer = renderer;
351  control.raycaster = new THREE.Raycaster();
352  control.raycaster.linePrecision = 10;
353  control.mouse_zoom_mesh = null; // zoom mesh, currently used in the zooming
354  control.block_ctxt = false; // require to block context menu command appearing after control ends, required in chrome which inject contextmenu when key released
355  control.block_mousemove = false; // when true, tooltip or cursor will not react on mouse move
356  control.cursor_changed = false;
357  control.control_changed = false;
358  control.control_active = false;
359  control.mouse_ctxt = { x:0, y: 0, on: false };
360  control.enable_zoom = enable_zoom;
361  control.enable_select = enable_select;
362 
363  control.Cleanup = function() {
364  if (JSROOT.gStyle.Zooming && JSROOT.gStyle.ZoomWheel)
365  this.domElement.removeEventListener( 'wheel', control_mousewheel);
366  if (this.enable_zoom || this.enable_select) {
367  this.domElement.removeEventListener( 'mousedown', control_mousedown);
368  this.domElement.removeEventListener( 'mouseup', control_mouseup);
369  }
370 
371  this.domElement.removeEventListener('dblclick', this.lstn_dblclick);
372  this.domElement.removeEventListener('contextmenu', this.lstn_contextmenu);
373  this.domElement.removeEventListener('mousemove', this.lstn_mousemove);
374  this.domElement.removeEventListener('mouseleave', this.lstn_mouseleave);
375 
376  this.dispose(); // this is from OrbitControl itself
377 
378  this.tooltip.hide();
379  delete this.tooltip;
380  delete this.painter;
381  delete this.camera;
382  delete this.scene;
383  delete this.renderer;
384  delete this.raycaster;
385  delete this.mouse_zoom_mesh;
386  }
387 
388  control.HideTooltip = function() {
389  this.tooltip.hide();
390  }
391 
392  control.GetMousePos = function(evnt, mouse) {
393  mouse.x = ('offsetX' in evnt) ? evnt.offsetX : evnt.layerX;
394  mouse.y = ('offsetY' in evnt) ? evnt.offsetY : evnt.layerY;
395  mouse.clientX = evnt.clientX;
396  mouse.clientY = evnt.clientY;
397  return mouse;
398  }
399 
400  control.GetOriginDirectionIntersects = function(origin, direction) {
401  this.raycaster.set(origin, direction);
402  var intersects = this.raycaster.intersectObjects(this.scene.children, true);
403  // painter may want to filter intersects
404  if (typeof this.painter.FilterIntersects == 'function')
405  intersects = this.painter.FilterIntersects(intersects);
406  return intersects;
407  }
408 
409  control.GetMouseIntersects = function(mouse) {
410  // domElement gives correct coordinate with canvas render, but isn't always right for webgl renderer
411  var sz = (this.renderer instanceof THREE.WebGLRenderer) ?
412  this.renderer.getSize(new THREE.Vector2()) :
413  this.renderer.domElement;
414 
415  var pnt = { x: mouse.x / sz.width * 2 - 1, y: -mouse.y / sz.height * 2 + 1 };
416 
417  this.camera.updateMatrix();
418  this.camera.updateMatrixWorld();
419  this.raycaster.setFromCamera( pnt, this.camera );
420  var intersects = this.raycaster.intersectObjects(this.scene.children, true);
421 
422  // painter may want to filter intersects
423  if (typeof this.painter.FilterIntersects == 'function')
424  intersects = this.painter.FilterIntersects(intersects);
425 
426  return intersects;
427  }
428 
429  control.DetectZoomMesh = function(evnt) {
430  var mouse = this.GetMousePos(evnt, {});
431  var intersects = this.GetMouseIntersects(mouse);
432  if (intersects)
433  for (var n=0;n<intersects.length;++n)
434  if (intersects[n].object.zoom)
435  return intersects[n];
436 
437  return null;
438  }
439 
440  control.ProcessDblClick = function(evnt) {
441  var intersect = this.DetectZoomMesh(evnt);
442  if (intersect && this.painter) {
443  this.painter.Unzoom(intersect.object.use_y_for_z ? "y" : intersect.object.zoom);
444  } else {
445  this.reset();
446  }
447  // this.painter.Render3D();
448  }
449 
450  control.ChangeEvent = function() {
451  this.mouse_ctxt.on = false; // disable context menu if any changes where done by orbit control
452  this.painter.zoom_changed_interactive = 1;
453  this.painter.Render3D(0);
454  this.control_changed = true;
455  }
456 
457  control.StartEvent = function() {
458  this.control_active = true;
459  this.block_ctxt = false;
460  this.mouse_ctxt.on = false;
461 
462  this.tooltip.hide();
463 
464  // do not reset here, problem of events sequence in orbitcontrol
465  // it issue change/start/stop event when do zooming
466  // control.control_changed = false;
467  }
468 
469  control.EndEvent = function() {
470  this.control_active = false;
471  if (this.mouse_ctxt.on) {
472  this.mouse_ctxt.on = false;
473  this.ContextMenu(this.mouse_ctxt, this.GetMouseIntersects(this.mouse_ctxt));
474  } /* else if (this.control_changed) {
475  // react on camera change when required
476  } */
477  this.control_changed = false;
478  }
479 
480  control.MainProcessContextMenu = function(evnt) {
481  evnt.preventDefault();
482  this.GetMousePos(evnt, this.mouse_ctxt);
483  if (this.control_active)
484  this.mouse_ctxt.on = true;
485  else
486  if (this.block_ctxt)
487  this.block_ctxt = false;
488  else
489  this.ContextMenu(this.mouse_ctxt, this.GetMouseIntersects(this.mouse_ctxt));
490  }
491 
492  control.ContextMenu = function(pos, intersects) {
493  // do nothing, function called when context menu want to be activated
494  }
495 
496  control.SwitchTooltip = function(on) {
497  this.block_mousemove = !on;
498  if (on===false) {
499  this.tooltip.hide();
500  this.RemoveZoomMesh();
501  }
502  }
503 
504  control.RemoveZoomMesh = function() {
505  if (this.mouse_zoom_mesh && this.mouse_zoom_mesh.object.ShowSelection())
506  this.painter.Render3D();
507  this.mouse_zoom_mesh = null; // in any case clear mesh, enable orbit control again
508  }
509 
510  control.MainProcessMouseMove = function(evnt) {
511  if (this.control_active && evnt.buttons && (evnt.buttons & 2))
512  this.block_ctxt = true; // if right button in control was active, block next context menu
513 
514  if (this.control_active || this.block_mousemove || !this.ProcessMouseMove) return;
515 
516  if (this.mouse_zoom_mesh) {
517  // when working with zoom mesh, need special handling
518 
519  var zoom2 = this.DetectZoomMesh(evnt), pnt2 = null;
520 
521  if (zoom2 && (zoom2.object === this.mouse_zoom_mesh.object)) {
522  pnt2 = zoom2.point;
523  } else {
524  pnt2 = this.mouse_zoom_mesh.object.GlobalIntersect(this.raycaster);
525  }
526 
527  if (pnt2) this.mouse_zoom_mesh.point2 = pnt2;
528 
529  if (pnt2 && this.painter.enable_highlight)
530  if (this.mouse_zoom_mesh.object.ShowSelection(this.mouse_zoom_mesh.point, pnt2))
531  this.painter.Render3D(0);
532 
533  this.tooltip.hide();
534  return;
535  }
536 
537  evnt.preventDefault();
538 
539  // extract mouse position
540  this.tmout_mouse = this.GetMousePos(evnt, {});
541  this.tmout_ttpos = this.tooltip ? this.tooltip.extract_pos(evnt) : null;
542 
543  if (this.tmout_handle) {
544  clearTimeout(this.tmout_handle);
545  delete this.tmout_handle;
546  }
547 
548  if (!this.mouse_tmout)
549  this.DelayedProcessMouseMove();
550  else
551  this.tmout_handle = setTimeout(this.DelayedProcessMouseMove.bind(this), this.mouse_tmout);
552  }
553 
554 
555  control.DelayedProcessMouseMove = function() {
556  // remove handle - allow to trigger new timeout
557  delete this.tmout_handle;
558 
559  var mouse = this.tmout_mouse,
560  intersects = this.GetMouseIntersects(mouse),
561  tip = this.ProcessMouseMove(intersects),
562  status_func = this.painter.GetShowStatusFunc();
563 
564  if (tip && status_func) {
565  var name = "", title = "", coord = "", info = "";
566  if (mouse) coord = mouse.x.toFixed(0)+ "," + mouse.y.toFixed(0);
567  if (typeof tip == "string") {
568  info = tip;
569  } else {
570  name = tip.name; title = tip.title;
571  if (tip.line) info = tip.line; else
572  if (tip.lines) { info = tip.lines.slice(1).join(' '); name = tip.lines[0]; }
573  }
574  status_func(name, title, info, coord);
575  }
576 
577  this.cursor_changed = false;
578  if (tip && this.painter && this.painter.IsTooltipAllowed()) {
579  this.tooltip.check_parent(this.painter.select_main().node());
580 
581  this.tooltip.show(tip, mouse);
582  this.tooltip.pos(this.tmout_ttpos);
583  } else {
584  this.tooltip.hide();
585  if (intersects)
586  for (var n=0;n<intersects.length;++n)
587  if (intersects[n].object.zoom) this.cursor_changed = true;
588  }
589 
590  document.body.style.cursor = this.cursor_changed ? 'pointer' : 'auto';
591  };
592 
593  control.MainProcessMouseLeave = function() {
594  // do not enter main event at all
595  if (this.tmout_handle) {
596  clearTimeout(this.tmout_handle);
597  delete this.tmout_handle;
598  }
599  this.tooltip.hide();
600  if (typeof this.ProcessMouseLeave === 'function')
601  this.ProcessMouseLeave();
602  if (this.cursor_changed) {
603  document.body.style.cursor = 'auto';
604  this.cursor_changed = false;
605  }
606  };
607 
608  function control_mousewheel(evnt) {
609  // try to handle zoom extra
610 
611  if (JSROOT.Painter.IsRender3DFired(control.painter) || control.mouse_zoom_mesh) {
612  evnt.preventDefault();
613  evnt.stopPropagation();
614  evnt.stopImmediatePropagation();
615  return; // already fired redraw, do not react on the mouse wheel
616  }
617 
618  var intersect = control.DetectZoomMesh(evnt);
619  if (!intersect) return;
620 
621  evnt.preventDefault();
622  evnt.stopPropagation();
623  evnt.stopImmediatePropagation();
624 
625  if (control.painter && (typeof control.painter.AnalyzeMouseWheelEvent == 'function')) {
626  var kind = intersect.object.zoom,
627  position = intersect.point[kind],
628  item = { name: kind, ignore: false };
629 
630  // z changes from 0..2*size_z3d, others -size_xy3d..+size_xy3d
631  if (kind!=="z") position = (position + control.painter.size_xy3d)/2/control.painter.size_xy3d;
632  else position = position/2/control.painter.size_z3d;
633 
634  control.painter.AnalyzeMouseWheelEvent(evnt, item, position, false);
635 
636  if ((kind==="z") && intersect.object.use_y_for_z) kind="y";
637 
638  control.painter.Zoom(kind, item.min, item.max);
639  }
640  }
641 
642  function control_mousedown(evnt) {
643 
644  // function used to hide some events from orbit control and redirect them to zooming rect
645 
646  if (control.mouse_zoom_mesh) {
647  evnt.stopImmediatePropagation();
648  evnt.stopPropagation();
649  return;
650  }
651 
652  // only left-button is considered
653  if ((evnt.button!==undefined) && (evnt.button !==0)) return;
654  if ((evnt.buttons!==undefined) && (evnt.buttons !== 1)) return;
655 
656  if (control.enable_zoom) {
657  control.mouse_zoom_mesh = control.DetectZoomMesh(evnt);
658  if (control.mouse_zoom_mesh) {
659  // just block orbit control
660  evnt.stopImmediatePropagation();
661  evnt.stopPropagation();
662  return;
663  }
664  }
665 
666  if (control.enable_select) {
667  control.mouse_select_pnt = control.GetMousePos(evnt, {});
668  }
669  }
670 
671  function control_mouseup(evnt) {
672 
673  if (control.mouse_zoom_mesh && control.mouse_zoom_mesh.point2 && control.painter.Get3DZoomCoord) {
674 
675  var kind = control.mouse_zoom_mesh.object.zoom,
676  pos1 = control.painter.Get3DZoomCoord(control.mouse_zoom_mesh.point, kind),
677  pos2 = control.painter.Get3DZoomCoord(control.mouse_zoom_mesh.point2, kind);
678 
679  if (pos1>pos2) { var v = pos1; pos1 = pos2; pos2 = v; }
680 
681  if ((kind==="z") && control.mouse_zoom_mesh.object.use_y_for_z) kind="y";
682 
683  if ((kind==="z") && control.mouse_zoom_mesh.object.use_y_for_z) kind="y";
684 
685  // try to zoom
686  if (pos1 < pos2)
687  if (control.painter.Zoom(kind, pos1, pos2))
688  control.mouse_zoom_mesh = null;
689  }
690 
691  // if selection was drawn, it should be removed and picture rendered again
692  if (control.enable_zoom)
693  control.RemoveZoomMesh();
694 
695  // only left-button is considered
696  //if ((evnt.button!==undefined) && (evnt.button !==0)) return;
697  //if ((evnt.buttons!==undefined) && (evnt.buttons !== 1)) return;
698 
699  if (control.enable_select && control.mouse_select_pnt) {
700 
701  var pnt = control.GetMousePos(evnt, {});
702 
703  var same_pnt = (pnt.x == control.mouse_select_pnt.x) && (pnt.y == control.mouse_select_pnt.y);
704  delete control.mouse_select_pnt;
705 
706  if (same_pnt) {
707  var intersects = control.GetMouseIntersects(pnt);
708  control.painter.ProcessMouseClick(pnt, intersects, evnt);
709  }
710  }
711  }
712 
713  control.MainProcessDblClick = function(evnt) {
714  this.ProcessDblClick(evnt);
715  }
716 
717  control.addEventListener( 'change', control.ChangeEvent.bind(control));
718  control.addEventListener( 'start', control.StartEvent.bind(control));
719  control.addEventListener( 'end', control.EndEvent.bind(control));
720 
721  control.lstn_contextmenu = control.MainProcessContextMenu.bind(control);
722  control.lstn_dblclick = control.MainProcessDblClick.bind(control);
723  control.lstn_mousemove = control.MainProcessMouseMove.bind(control);
724  control.lstn_mouseleave = control.MainProcessMouseLeave.bind(control);
725 
726  renderer.domElement.addEventListener('dblclick', control.lstn_dblclick);
727  renderer.domElement.addEventListener('contextmenu', control.lstn_contextmenu);
728  renderer.domElement.addEventListener('mousemove', control.lstn_mousemove);
729  renderer.domElement.addEventListener('mouseleave', control.lstn_mouseleave);
730 
731  return control;
732  }
733 
738  JSROOT.Painter.DisposeThreejsObject = function(obj, only_childs) {
739  if (!obj) return;
740 
741  if (obj.children) {
742  for (var i = 0; i < obj.children.length; i++)
743  JSROOT.Painter.DisposeThreejsObject(obj.children[i]);
744  }
745 
746  if (only_childs) {
747  obj.children = [];
748  return;
749  }
750 
751  obj.children = undefined;
752 
753  if (obj.geometry) {
754  obj.geometry.dispose();
755  obj.geometry = undefined;
756  }
757  if (obj.material) {
758  if (obj.material.map) {
759  obj.material.map.dispose();
760  obj.material.map = undefined;
761  }
762  obj.material.dispose();
763  obj.material = undefined;
764  }
765 
766  // cleanup jsroot fields to simplify browser cleanup job
767  delete obj.painter;
768  delete obj.bins_index;
769  delete obj.tooltip;
770  delete obj.stack; // used in geom painter
771  delete obj.drawn_highlight; // special highlight object
772 
773  obj = undefined;
774  }
775 
776  JSROOT.Painter.createLineSegments = function(arr, material, index, only_geometry) {
777  // prepare geometry for THREE.LineSegments
778  // If required, calculate lineDistance attribute for dashed geometries
779 
780  var geom = new THREE.BufferGeometry();
781 
782  geom.addAttribute( 'position', arr instanceof Float32Array ? new THREE.BufferAttribute( arr, 3 ) : new THREE.Float32BufferAttribute( arr, 3 ) );
783  if (index) geom.setIndex( new THREE.BufferAttribute(index, 1) );
784 
785  if (material.isLineDashedMaterial) {
786 
787  var v1 = new THREE.Vector3(),
788  v2 = new THREE.Vector3(),
789  d = 0, distances = null;
790 
791  if (index) {
792  distances = new Float32Array(index.length);
793  for (var n=0; n<index.length; n+=2) {
794  var i1 = index[n], i2 = index[n+1];
795  v1.set(arr[i1],arr[i1+1],arr[i1+2]);
796  v2.set(arr[i2],arr[i2+1],arr[i2+2]);
797  distances[n] = d;
798  d += v2.distanceTo( v1 );
799  distances[n+1] = d;
800  }
801  } else {
802  distances = new Float32Array(arr.length/3);
803  for (var n=0; n<arr.length; n+=6) {
804  v1.set(arr[n],arr[n+1],arr[n+2]);
805  v2.set(arr[n+3],arr[n+4],arr[n+5]);
806  distances[n/3] = d;
807  d += v2.distanceTo( v1 );
808  distances[n/3+1] = d;
809  }
810  }
811  geom.addAttribute( 'lineDistance', new THREE.BufferAttribute(distances, 1) );
812  }
813 
814  return only_geometry ? geom : new THREE.LineSegments(geom, material);
815  }
816 
817  JSROOT.Painter.Box3D = {
818  Vertices: [ new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 0),
819  new THREE.Vector3(1, 0, 1), new THREE.Vector3(1, 0, 0),
820  new THREE.Vector3(0, 1, 0), new THREE.Vector3(0, 1, 1),
821  new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, 1) ],
822  Indexes: [ 0,2,1, 2,3,1, 4,6,5, 6,7,5, 4,5,1, 5,0,1, 7,6,2, 6,3,2, 5,7,0, 7,2,0, 1,3,4, 3,6,4 ],
823  Normals: [ 1,0,0, -1,0,0, 0,1,0, 0,-1,0, 0,0,1, 0,0,-1 ],
824  Segments: [0, 2, 2, 7, 7, 5, 5, 0, 1, 3, 3, 6, 6, 4, 4, 1, 1, 0, 3, 2, 6, 7, 4, 5] // segments addresses Vertices
825  };
826 
827  // these segments address vertices from the mesh, we can use positions from box mesh
828  JSROOT.Painter.Box3D.MeshSegments = (function() {
829  var box3d = JSROOT.Painter.Box3D,
830  arr = new Int32Array(box3d.Segments.length);
831 
832  for (var n=0;n<arr.length;++n) {
833  for (var k=0;k<box3d.Indexes.length;++k)
834  if (box3d.Segments[n] === box3d.Indexes[k]) {
835  arr[n] = k; break;
836  }
837  }
838  return arr;
839  })();
840 
841  JSROOT.Painter.IsRender3DFired = function(painter) {
842  if (!painter || painter.renderer === undefined) return false;
843 
844  return painter.render_tmout !== undefined; // when timeout configured, object is prepared for rendering
845  }
846 
847  // ==============================================================================
848 
849  function InteractiveControl() {}
850 
851  InteractiveControl.prototype.cleanup = function() {}
852 
853  InteractiveControl.prototype.extractIndex = function(intersect) { return undefined; }
854 
855  InteractiveControl.prototype.setSelected = function(col, indx) {}
856 
857  InteractiveControl.prototype.setHighlight = function(col, indx) {}
858 
859  InteractiveControl.prototype.checkHighlightIndex = function(indx) { return undefined; }
860 
861  // ==============================================================================
862 
865  function PointsControl(mesh) {
866  InteractiveControl.call(this);
867  this.mesh = mesh;
868  }
869 
870  PointsControl.prototype = Object.create(InteractiveControl.prototype);
871 
872  PointsControl.prototype.cleanup = function() {
873  if (!this.mesh) return;
874  delete this.mesh.is_selected;
875  this.createSpecial(null);
876  delete this.mesh;
877  }
878 
879  PointsControl.prototype.extractIndex = function(intersect) {
880  return intersect && intersect.index!==undefined ? intersect.index : undefined;
881  }
882 
883  PointsControl.prototype.setSelected = function(col, indx) {
884  var m = this.mesh;
885  if ((m.select_col == col) && (m.select_indx == indx)) {
886  console.log("Reset selection");
887  col = null; indx = undefined;
888  }
889  m.select_col = col;
890  m.select_indx = indx;
891  this.createSpecial(col, indx);
892  return true;
893  }
894 
895  PointsControl.prototype.setHighlight = function(col, indx) {
896  var m = this.mesh;
897  m.h_index = indx;
898  if (col)
899  this.createSpecial(col, indx);
900  else
901  this.createSpecial(m.select_col, m.select_indx);
902  return true;
903  }
904 
905  PointsControl.prototype.createSpecial = function(color, index) {
906  var m = this.mesh;
907  if (!color) {
908  if (m.js_special) {
909  m.remove(m.js_special);
910  JSROOT.Painter.DisposeThreejsObject(m.js_special);
911  delete m.js_special;
912  }
913  return;
914  }
915 
916  if (!m.js_special) {
917  var geom = new THREE.BufferGeometry();
918  geom.addAttribute( 'position', m.geometry.getAttribute("position"));
919  var material = new THREE.PointsMaterial( { size: m.material.size*2, color: color } );
920  material.sizeAttenuation = m.material.sizeAttenuation;
921 
922  m.js_special = new THREE.Points(geom, material);
923  m.js_special.jsroot_special = true; // special object, exclude from intersections
924  m.add(m.js_special);
925  }
926 
927  if (color) m.js_special.material.color = new THREE.Color(color);
928  if (index !== undefined) m.js_special.geometry.setDrawRange(index, 1);
929  }
930 
931 
932  function PointsCreator(size, iswebgl, scale) {
933  this.webgl = (iswebgl === undefined) ? true : iswebgl;
934  this.scale = scale || 1.;
935 
936  this.pos = new Float32Array(size*3);
937  this.geom = new THREE.BufferGeometry();
938  this.geom.addAttribute( 'position', new THREE.BufferAttribute( this.pos, 3 ) );
939  this.indx = 0;
940  }
941 
942  PointsCreator.prototype.AddPoint = function(x,y,z) {
943  this.pos[this.indx] = x;
944  this.pos[this.indx+1] = y;
945  this.pos[this.indx+2] = z;
946  this.indx+=3;
947  }
948 
950  PointsCreator.prototype.AssignCallback = function(callback) {
951  this.callback = callback;
952  }
953 
954  PointsCreator.prototype.Complete = function(arg) {
955 
956  var material;
957 
958  if (this.texture) {
959  if ((arg == 'loaded') && this.texture.onUpdate) this.texture.onUpdate( this.texture );
960  if (this._did_create) return;
961  material = new THREE.PointsMaterial( { size: (this.webgl ? 3 : 1) * this.scale, map: this.texture, transparent: true } );
962  } else {
963  if (this._did_create) return;
964  material = new THREE.PointsMaterial( { size: (this.webgl ? 3 : 1) * this.scale * this.k, color: this.color } );
965  }
966 
967  this._did_create = true;
968 
969  var pnts = new THREE.Points(this.geom, material);
970  pnts.nvertex = 1;
971 
972  if (!this.callback)
973  return pnts;
974 
975  JSROOT.CallBack(this.callback, pnts);
976  }
977 
978  PointsCreator.prototype.CreatePoints = function(args) {
979 
980  if (typeof args !== 'object') args = { color: args };
981  if (!args.color) args.color = 'black';
982 
983  this.k = 1;
984  this.color = args.color;
985 
986  this._did_create = false;
987 
988  // special dots
989  if (args.style === 1) this.k = 0.3; else
990  if (args.style === 6) this.k = 0.5; else
991  if (args.style === 7) this.k = 0.7;
992 
993  // this is plain creation of points, no texture loading
994  if (!args.style || (this.k !== 1) || JSROOT.BatchMode)
995  return this.Complete();
996 
997  var handler = new JSROOT.TAttMarkerHandler({ style: args.style, color: args.color, size: 8 });
998 
999  var plainSVG = '<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg">' +
1000  '<path d="' + handler.create(32,32) + '" stroke="' + handler.getStrokeColor() + '" fill="' + handler.getFillColor() + '"/></svg>';
1001 
1002  this.texture = new THREE.Texture();
1003  this.texture.needsUpdate = true;
1004  this.texture.format = THREE.RGBAFormat;
1005  this.texture.image = document.createElement('img');
1006 
1007  this.texture.image.onload = this.Complete.bind(this,'loaded')
1008 
1009  this.texture.image.src = 'data:image/svg+xml;utf8,' + plainSVG;
1010 
1011  if (!this.callback)
1012  return this.Complete();
1013  }
1014 
1015  // ==============================================================================
1016 
1017  function Create3DLineMaterial(painter, obj) {
1018  if (!painter || !obj) return null;
1019 
1020  var lcolor = painter.get_color(obj.fLineColor),
1021  material = null,
1022  style = obj.fLineStyle ? JSROOT.Painter.root_line_styles[obj.fLineStyle] : "",
1023  dash = style ? style.split(",") : [];
1024 
1025  if (dash && dash.length>=2)
1026  material = new THREE.LineDashedMaterial( { color: lcolor, dashSize: parseInt(dash[0]), gapSize: parseInt(dash[1]) } );
1027  else
1028  material = new THREE.LineBasicMaterial({ color: lcolor });
1029 
1030  if (obj.fLineWidth && (obj.fLineWidth>1) && !JSROOT.browser.isIE) material.linewidth = obj.fLineWidth;
1031 
1032  return material;
1033  }
1034 
1035  // ============================================================================================================
1036 
1037  function drawPolyLine3D() {
1038  var line = this.GetObject(),
1039  main = this.frame_painter();
1040 
1041  if (!main || !main.mode3d || !main.toplevel || !line) return;
1042 
1043  var fN, fP, fOption, pnts = [];
1044 
1045  if (line._blob && (line._blob.length==4)) {
1046  // workaround for custom streamer for JSON, should be resolved
1047  fN = line._blob[1];
1048  fP = line._blob[2];
1049  fOption = line._blob[3];
1050  } else {
1051  fN = line.fN;
1052  fP = line.fP;
1053  fOption = line.fOption;
1054  }
1055 
1056  for (var n=3;n<3*fN;n+=3)
1057  pnts.push(main.grx(fP[n-3]), main.gry(fP[n-2]), main.grz(fP[n-1]),
1058  main.grx(fP[n]), main.gry(fP[n+1]), main.grz(fP[n+2]));
1059 
1060  var lines = JSROOT.Painter.createLineSegments(pnts, Create3DLineMaterial(this, line));
1061 
1062  main.toplevel.add(lines);
1063  }
1064 
1065  // ==============================================================================================
1066 
1067 
1068 
1069  JSROOT.Painter.PointsCreator = PointsCreator;
1070  JSROOT.Painter.InteractiveControl = InteractiveControl;
1071  JSROOT.Painter.PointsControl = PointsControl;
1072 
1073  JSROOT.Painter.drawPolyLine3D = drawPolyLine3D;
1074 
1075  JSROOT.Painter.Create3DLineMaterial = Create3DLineMaterial;
1076 
1077  return JSROOT;
1078 
1079 }));
1080