otsdaq_utilities  v2_05_02_indev
JSRoot3DPainter.js
1 
4 (function( factory ) {
5  if ( typeof define === "function" && define.amd ) {
6  // AMD. Register as an anonymous module.
7  define( ['d3', 'JSRootPainter', 'threejs_all'], factory );
8  } else {
9 
10  if (typeof JSROOT == 'undefined')
11  throw new Error('JSROOT is not defined', 'JSRoot3DPainter.js');
12 
13  if (typeof d3 != 'object')
14  throw new Error('This extension requires d3.v3.js', 'JSRoot3DPainter.js');
15 
16  if (typeof JSROOT.Painter != 'object')
17  throw new Error('JSROOT.Painter is not defined', 'JSRoot3DPainter.js');
18 
19  if (typeof THREE == 'undefined')
20  throw new Error('THREE is not defined', 'JSRoot3DPainter.js');
21 
22  factory(d3, JSROOT);
23  }
24 } (function(d3, JSROOT) {
25 
26  JSROOT.Painter.TestWebGL = function() {
27  // return true if WebGL should be used
33  if (JSROOT.gStyle.NoWebGL) return false;
34 
35  if ('_Detect_WebGL' in this) return this._Detect_WebGL;
36 
37  try {
38  var canvas = document.createElement( 'canvas' );
39  this._Detect_WebGL = !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) );
40  //res = !!window.WebGLRenderingContext && !!document.createElement('canvas').getContext('experimental-webgl');
41  } catch (e) {
42  return false;
43  }
44 
45  return this._Detect_WebGL;
46  }
47 
48  JSROOT.Painter.add3DInteraction = function() {
49  // add 3D mouse interactive functions
50 
51  var painter = this;
52  var mouseX, mouseY, distXY = 0, mouseDowned = false;
53  var INTERSECTED = null;
54 
55  var tooltip = {
56  tt: null, cont: null,
57  pos : function(e) {
58  if (this.tt === null) return;
59  var u = JSROOT.browser.isIE ? (event.clientY + document.documentElement.scrollTop) : e.pageY;
60  var l = JSROOT.browser.isIE ? (event.clientX + document.documentElement.scrollLeft) : e.pageX;
61 
62  this.tt.style.top = (u + 15) + 'px';
63  this.tt.style.left = (l + 3) + 'px';
64  },
65  show : function(v) {
66  if (JSROOT.gStyle.Tooltip <= 0) return;
67  if (this.tt === null) {
68  this.tt = document.createElement('div');
69  this.tt.setAttribute('class', 'jsroot');
70  var t = document.createElement('div');
71  t.setAttribute('class', 'tt3d_border');
72  this.cont = document.createElement('div');
73  this.cont.setAttribute('class', 'tt3d_cont');
74  var b = document.createElement('div');
75  b.setAttribute('class', 'tt3d_border');
76  this.tt.appendChild(t);
77  this.tt.appendChild(this.cont);
78  this.tt.appendChild(b);
79  document.body.appendChild(this.tt);
80  this.tt.style.opacity = 1;
81  this.tt.style.filter = 'alpha(opacity=1)';
82  this.tt.style.position = 'absolute';
83  this.tt.style.display = 'block';
84  }
85  this.cont.innerHTML = v;
86  this.tt.style.width = 'auto'; // let it be automatically resizing...
87  if (JSROOT.browser.isIE)
88  this.tt.style.width = this.tt.offsetWidth;
89  },
90  hide : function() {
91  if (this.tt !== null)
92  document.body.removeChild(this.tt);
93  this.tt = null;
94  }
95  };
96 
97  var raycaster = new THREE.Raycaster();
98  var do_bins_highlight = painter.first_render_tm < 2000;
99 
100  function findIntersection(mouse) {
101  // find intersections
102 
103  if (JSROOT.gStyle.Tooltip <= 0) return tooltip.hide();
104 
105  raycaster.setFromCamera( mouse, painter.camera );
106  var intersects = raycaster.intersectObjects(painter.scene.children, true);
107  if (intersects.length > 0) {
108  var pick = null;
109  for (var i = 0; i < intersects.length; ++i) {
110  if (('name' in intersects[i].object) && (intersects[i].object.name.length > 0)) {
111  pick = intersects[i].object;
112  break;
113  }
114  }
115  if ((pick!==null) && (INTERSECTED !== pick)) {
116  if (INTERSECTED && do_bins_highlight && ('emissive' in INTERSECTED.material))
117  INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
118  INTERSECTED = pick;
119  if (do_bins_highlight && ('emissive' in INTERSECTED.material)) {
120  INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
121  INTERSECTED.material.emissive.setHex(0x5f5f5f);
122  painter.Render3D(0);
123  }
124 
125  tooltip.show(INTERSECTED.name, 200);
126  }
127  } else {
128  if (INTERSECTED && do_bins_highlight && ('emissive' in INTERSECTED.material)) {
129  INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
130  painter.Render3D(0);
131  }
132  INTERSECTED = null;
133  tooltip.hide();
134  }
135  };
136 
137  function coordinates(e) {
138  if ('changedTouches' in e) return e.changedTouches;
139  if ('touches' in e) return e.touches;
140  return [e];
141  }
142 
143  function mousedown(e) {
144  tooltip.hide();
145  e.preventDefault();
146 
147  var arr = coordinates(e);
148  if (arr.length == 2) {
149  distXY = Math.sqrt(Math.pow(arr[0].pageX - arr[1].pageX, 2) + Math.pow(arr[0].pageY - arr[1].pageY, 2));
150  } else {
151  mouseX = arr[0].pageX;
152  mouseY = arr[0].pageY;
153  }
154  mouseDowned = true;
155 
156  }
157 
158  painter.renderer.domElement.addEventListener('touchstart', mousedown);
159  painter.renderer.domElement.addEventListener('mousedown', mousedown);
160 
161  function mousemove(e) {
162  var arr = coordinates(e);
163 
164  if (mouseDowned) {
165  if (arr.length == 2) {
166  var dist = Math.sqrt(Math.pow(arr[0].pageX - arr[1].pageX, 2) + Math.pow(arr[0].pageY - arr[1].pageY, 2));
167 
168  var delta = (dist-distXY)/(dist+distXY);
169  distXY = dist;
170  if (delta === 1.) return;
171 
172  painter.camera.position.x += delta * painter.size3d * 10;
173  painter.camera.position.y += delta * painter.size3d * 10;
174  painter.camera.position.z -= delta * painter.size3d * 10;
175  } else {
176  var moveX = arr[0].pageX - mouseX;
177  var moveY = arr[0].pageY - mouseY;
178  var length = painter.camera.position.length();
179  var ddd = length > painter.size3d ? 0.001*length/painter.size3d : 0.01;
180  // limited X rotate in -45 to 135 deg
181  //if ((moveY > 0 && painter.toplevel.rotation.x < Math.PI * 3 / 4)
182  // || (moveY < 0 && painter.toplevel.rotation.x > -Math.PI / 4))
183  // painter.toplevel.rotation.x += moveX * 0.02;
184  painter.toplevel.rotation.z += moveX * ddd;
185  painter.toplevel.rotation.x += moveY * ddd;
186  painter.toplevel.rotation.y -= moveY * ddd;
187  mouseX = arr[0].pageX;
188  mouseY = arr[0].pageY;
189  }
190  painter.Render3D(0);
191  } else
192  if (arr.length == 1) {
193  var mouse_x = ('offsetX' in arr[0]) ? arr[0].offsetX : arr[0].layerX;
194  var mouse_y = ('offsetY' in arr[0]) ? arr[0].offsetY : arr[0].layerY;
195  mouse = { x: (mouse_x / painter.renderer.domElement.width) * 2 - 1,
196  y: -(mouse_y / painter.renderer.domElement.height) * 2 + 1 };
197  findIntersection(mouse);
198  tooltip.pos(arr[0]);
199  } else {
200  tooltip.hide();
201  }
202 
203  e.stopPropagation();
204  e.preventDefault();
205  }
206 
207  painter.renderer.domElement.addEventListener('touchmove', mousemove);
208  painter.renderer.domElement.addEventListener('mousemove', mousemove);
209 
210  function mouseup(e) {
211  mouseDowned = false;
212  tooltip.hide();
213  distXY = 0;
214  }
215 
216  painter.renderer.domElement.addEventListener('touchend', mouseup);
217  painter.renderer.domElement.addEventListener('touchcancel', mouseup);
218  painter.renderer.domElement.addEventListener('mouseup', mouseup);
219 
220  function mousewheel(event) {
221  event.preventDefault();
222  event.stopPropagation();
223 
224  var delta = 0;
225  if ( event.wheelDelta ) {
226  // WebKit / Opera / Explorer 9
227  delta = event.wheelDelta / 400;
228  } else if ( event.detail ) {
229  // Firefox
230  delta = - event.detail / 30;
231  }
232  painter.camera.position.x -= delta * painter.size3d;
233  painter.camera.position.y -= delta * painter.size3d;
234  painter.camera.position.z += delta * painter.size3d;
235  painter.Render3D(0);
236  }
237 
238  painter.renderer.domElement.addEventListener( 'mousewheel', mousewheel, false );
239  painter.renderer.domElement.addEventListener( 'MozMousePixelScroll', mousewheel, false ); // firefox
240 
241 
242  painter.renderer.domElement.addEventListener('mouseleave', function() {
243  tooltip.hide();
244  });
245 
246 
247  painter.renderer.domElement.addEventListener('contextmenu', function(e) {
248  e.preventDefault();
249  tooltip.hide();
250 
251  painter.ShowContextMenu("hist", e);
252  });
253 
254  }
255 
256  JSROOT.Painter.HPainter_Create3DScene = function(arg) {
257 
258  if ((arg!==null) && (arg<0)) {
259  this.clear_3d_canvas();
260  delete this.size3d;
261  delete this.scene;
262  delete this.toplevel;
263  delete this.camera;
264  delete this.renderer;
265  if ('render_tmout' in this) {
266  clearTimeout(this.render_tmout);
267  delete this.render_tmout;
268  }
269  return;
270  }
271 
272  if ('toplevel' in this) {
273  // it is indication that all 3D object created, just replace it with empty
274 
275  var newtop = new THREE.Object3D();
276 
277  newtop.rotation.x = this.toplevel.rotation.x;
278  newtop.rotation.y = this.toplevel.rotation.y;
279 
280  this.scene.remove(this.toplevel);
281 
282  this.scene.add(newtop);
283 
284  this.toplevel = newtop;
285 
286  return;
287  }
288 
289  var size = this.size_for_3d();
290 
291  this.size3d = 100;
292 
293  // three.js 3D drawing
294  this.scene = new THREE.Scene();
295  //scene.fog = new THREE.Fog(0xffffff, 500, 3000);
296 
297  this.toplevel = new THREE.Object3D();
298  //this.toplevel.rotation.x = 30 * Math.PI / 180;
299  //this.toplevel.rotation.y = 30 * Math.PI / 180;
300  this.scene.add(this.toplevel);
301  this.scene_width = size.width;
302  this.scene_height = size.height
303 
304  this.camera = new THREE.PerspectiveCamera(45, this.scene_width / this.scene_height, 1, 40*this.size3d);
305  var pointLight = new THREE.PointLight(0xcfcfcf);
306  this.camera.add( pointLight );
307  pointLight.position.set( this.size3d / 10, this.size3d / 10, this.size3d / 10 );
308  this.camera.position.set(-3*this.size3d, -3*this.size3d, 3*this.size3d);
309  this.camera.up = new THREE.Vector3(0,0,1);
310  this.camera.lookAt(new THREE.Vector3(0,0,this.size3d));
311  this.scene.add( this.camera );
312 
313  var webgl = JSROOT.Painter.TestWebGL();
314 
315  this.renderer = webgl ? new THREE.WebGLRenderer({ antialias : true, alpha: true }) :
316  new THREE.CanvasRenderer({ antialias : true, alpha: true });
317  //renderer.setClearColor(0xffffff, 1);
318  // renderer.setClearColor(0x0, 0);
319  this.renderer.setSize(this.scene_width, this.scene_height);
320 
321  this.add_3d_canvas(size, this.renderer.domElement);
322 
323  this['DrawXYZ'] = JSROOT.Painter.HPainter_DrawXYZ;
324  this['Render3D'] = JSROOT.Painter.Render3D;
325  this['Resize3D'] = JSROOT.Painter.Resize3D;
326 
327  this.first_render_tm = 0;
328  }
329 
330  JSROOT.Painter.HPainter_DrawXYZ = function() {
331 
332  var grminx = -this.size3d, grmaxx = this.size3d,
333  grminy = -this.size3d, grmaxy = this.size3d,
334  grminz = 0, grmaxz = 2*this.size3d,
335  textsize = Math.round(this.size3d * 0.07),
336  bothsides = (this.size3d !== 0),
337  xmin = this.xmin, xmax = this.xmax,
338  ymin = this.ymin, ymax = this.ymax,
339  zmin = this.zmin, zmax = this.zmax,
340  histo = this.histo;
341 
342  if (this.size3d === 0) {
343  grminx = this.xmin; grmaxx = this.xmax;
344  grminy = this.ymin; grmaxy = this.ymax;
345  grminz = this.zmin; grmaxz = this.zmax;
346  textsize = (grmaxz - grminz) * 0.05;
347  }
348 
349  if (('zoom_xmin' in this) && ('zoom_xmax' in this) && (this.zoom_xmin !== this.zoom_xmax)) {
350  xmin = this.zoom_xmin; xmax = this.zoom_xmax;
351  }
352 
353  if (('zoom_ymin' in this) && ('zoom_ymax' in this) && (this.zoom_ymin !== this.zoom_ymax)) {
354  ymin = this.zoom_ymin; ymax = this.zoom_ymax;
355  }
356 
357  if (('zoom_zmin' in this) && ('zoom_zmax' in this) && (this.zoom_zmin !== this.zoom_zmax)) {
358  zmin = this.zoom_zmin; zmax = this.zoom_zmax;
359  }
360 
361  if (this.options.Logx) {
362  if (xmax <= 0) xmax = 1.;
363  if ((xmin <= 0) && (this.nbinsx > 0))
364  for (var i=0;i<this.nbinsx;++i) {
365  xmin = Math.max(xmin, this.GetBinX(i));
366  if (xmin>0) break;
367  }
368  if (xmin <= 0) xmin = 1e-4*xmax;
369  this.tx = d3.scale.log();
370  this.x_kind = "log";
371  } else {
372  this.tx = d3.scale.linear();
373  this.x_kind = "lin";
374  }
375  this.tx.domain([ xmin, xmax ]).range([ grminx, grmaxx ]);
376  this.x_handle = new JSROOT.TAxisPainter(histo ? histo.fXaxis : null);
377  this.x_handle.SetAxisConfig("xaxis", this.x_kind, this.tx, this.xmin, this.xmax, xmin, xmax);
378  this.x_handle.CreateFormatFuncs();
379 
380  if (this.options.Logy) {
381  if (ymax <= 0) ymax = 1.;
382  if ((ymin <= 0) && (this.nbinsy>0))
383  for (var i=0;i<this.nbinsy;++i) {
384  ymin = Math.max(ymin, this.GetBinY(i));
385  if (ymin>0) break;
386  }
387 
388  if (ymin <= 0) ymin = 1e-4*ymax;
389  this.ty = d3.scale.log();
390  this.y_kind = "log";
391  } else {
392  this.ty = d3.scale.linear();
393  this.y_kind = "lin";
394  }
395  this.ty.domain([ ymin, ymax ]).range([ grminy, grmaxy ]);
396  this.y_handle = new JSROOT.TAxisPainter(histo ? histo.fYaxis : null);
397  this.y_handle.SetAxisConfig("yaxis", this.y_kind, this.ty, this.ymin, this.ymax, ymin, ymax);
398  this.y_handle.CreateFormatFuncs();
399 
400  if (this.options.Logz) {
401  if (zmax <= 0) zmax = 1;
402  if (zmin <= 0) zmin = 1e-4*zmax;
403  this.tz = d3.scale.log();
404  this.z_kind = "log";
405  } else {
406  this.tz = d3.scale.linear();
407  this.z_kind = "lin";
408  }
409  this.tz.domain([ zmin, zmax ]).range([ grminz, grmaxz ]);
410 
411  this.z_handle = new JSROOT.TAxisPainter(histo ? histo.fZaxis : null);
412  this.z_handle.SetAxisConfig("zaxis", this.z_kind, this.tz, this.zmin, this.zmax, zmin, zmax);
413  this.z_handle.CreateFormatFuncs();
414 
415  var textMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });
416  var lineMaterial = new THREE.LineBasicMaterial({ color: 0x000000 });
417 
418  var ticklen = textsize * 0.5, text, tick;
419 
420  var xticks = this.x_handle.CreateTicks();
421 
422  // geometry used for the tick drawing
423  var geometry = new THREE.Geometry();
424  geometry.vertices.push(new THREE.Vector3(0, 0, 0));
425  geometry.vertices.push(new THREE.Vector3(0, -1, -1));
426 
427  while (xticks.next()) {
428  var grx = xticks.grpos;
429  var is_major = (xticks.kind===1);
430  var lbl = this.x_handle.format(xticks.tick, true, true);
431  if (xticks.last_major()) lbl = "x"; else
432  if (lbl === null) { is_major = false; lbl = ""; }
433  var plen = (is_major ? ticklen : ticklen * 0.6) * Math.sin(Math.PI/4);
434 
435  if (is_major) {
436  var text3d = new THREE.TextGeometry(lbl, { size : textsize, height : 0, curveSegments : 10 });
437  text3d.computeBoundingBox();
438  var centerOffset = 0.5 * (text3d.boundingBox.max.x - text3d.boundingBox.min.x);
439 
440  if (bothsides) {
441  text = new THREE.Mesh(text3d, textMaterial);
442  text.position.set(grx + centerOffset, grmaxy + plen + textsize, grminz - plen - textsize);
443  text.rotation.x = Math.PI*3/4;
444  text.rotation.y = Math.PI;
445  text.name = "X axis";
446  this.toplevel.add(text);
447  }
448 
449  text = new THREE.Mesh(text3d, textMaterial);
450  text.position.set(grx - centerOffset, grminy - plen - textsize, grminz - plen - textsize);
451  text.rotation.x = Math.PI/4;
452  text.name = "X axis";
453  this.toplevel.add(text);
454  }
455 
456  if (bothsides) {
457  tick = new THREE.Line(geometry, lineMaterial);
458  tick.position.set(grx,grmaxy, grminz);
459  tick.scale.set(1,plen,plen);
460  tick.rotation.z = Math.PI;
461  tick.name = "X axis: " + this.x_handle.format(xticks.tick);
462  this.toplevel.add(tick);
463  }
464 
465  tick = new THREE.Line(geometry, lineMaterial);
466  tick.position.set(grx,grminy,grminz);
467  tick.scale.set(1,plen,plen);
468  tick.name = "X axis: " + this.x_handle.format(xticks.tick);
469  this.toplevel.add(tick);
470  }
471 
472  var yticks = this.y_handle.CreateTicks();
473  geometry = new THREE.Geometry();
474  geometry.vertices.push(new THREE.Vector3(0, 0, 0));
475  geometry.vertices.push(new THREE.Vector3(-1, 0, -1));
476 
477  while (yticks.next()) {
478  var gry = yticks.grpos;
479  var is_major = (yticks.kind===1);
480  var lbl = this.y_handle.format(yticks.tick, true, true);
481  if (yticks.last_major()) lbl = "y"; else
482  if (lbl === null) { is_major = false; lbl = ""; }
483  var plen = (is_major ? ticklen : ticklen*0.6) * Math.sin(Math.PI/4);
484 
485  if (is_major) {
486  var text3d = new THREE.TextGeometry(lbl, { size : textsize, height : 0, curveSegments : 10 });
487 
488  text3d.computeBoundingBox();
489  var centerOffset = 0.5 * (text3d.boundingBox.max.x - text3d.boundingBox.min.x);
490 
491  if (bothsides) {
492  text = new THREE.Mesh(text3d, textMaterial);
493  text.position.set(grmaxx + plen + textsize, gry + centerOffset, grminz - plen - textsize);
494  text.rotation.y = Math.PI / 4;
495  text.rotation.z = Math.PI / 2;
496  text.name = "Y axis";
497  this.toplevel.add(text);
498  }
499 
500  text = new THREE.Mesh(text3d, textMaterial);
501  text.position.set(grminx - plen - textsize, gry + centerOffset, grminz - plen - textsize);
502  text.rotation.y = -Math.PI / 4;
503  text.rotation.z = -Math.PI / 2;
504  text.name = "Y axis";
505  this.toplevel.add(text);
506  }
507  if (bothsides) {
508  tick = new THREE.Line(geometry, lineMaterial);
509  tick.position.set(grmaxx,gry,grminz);
510  tick.scale.set(plen,1,plen);
511  tick.rotation.z = Math.PI;
512  tick.name = "Y axis " + this.y_handle.format(yticks.tick);
513  this.toplevel.add(tick);
514  }
515  tick = new THREE.Line(geometry, lineMaterial);
516  tick.position.set(grminx,gry,grminz);
517  tick.scale.set(plen,1, plen);
518  tick.name = "Y axis " + this.y_handle.format(yticks.tick);
519  this.toplevel.add(tick);
520  }
521 
522  var zticks = this.z_handle.CreateTicks();
523  geometry = new THREE.Geometry();
524  geometry.vertices.push(new THREE.Vector3(0, 0, 0));
525  geometry.vertices.push(new THREE.Vector3(-1, 1, 0));
526  while (zticks.next()) {
527  var grz = zticks.grpos;
528  var is_major = zticks.kind == 1;
529 
530  var lbl = this.z_handle.format(zticks.tick, true, true);
531  if (lbl === null) { is_major = false; lbl = ""; }
532  var plen = (is_major ? ticklen : ticklen * 0.6) * Math.sin(Math.PI/4);
533 
534  if (is_major) {
535  var text3d = new THREE.TextGeometry(lbl, { size : textsize, height : 0, curveSegments : 10 });
536 
537  text3d.computeBoundingBox();
538  var offset = 0.8 * (text3d.boundingBox.max.x - text3d.boundingBox.min.x) + 0.7 * textsize;
539 
540  var textz = grz - 0.4*textsize;
541 
542  if (bothsides) {
543  text = new THREE.Mesh(text3d, textMaterial);
544  text.position.set(grmaxx + offset, grmaxy + offset, textz);
545  text.rotation.x = 0.5*Math.PI;
546  text.rotation.y = -0.75 * Math.PI;
547  text.name = "Z axis";
548  this.toplevel.add(text);
549 
550  text = new THREE.Mesh(text3d, textMaterial);
551  text.position.set(grmaxx + offset, grminy - offset, textz);
552  text.rotation.x = 0.5*Math.PI;
553  text.rotation.y = 0.75*Math.PI;
554  text.name = "Z axis";
555  this.toplevel.add(text);
556 
557  text = new THREE.Mesh(text3d, textMaterial);
558  text.position.set(grminx - offset, grminy - offset, textz);
559  text.rotation.x = 0.5*Math.PI;
560  text.rotation.y = 0.25*Math.PI;
561  text.name = "Z axis";
562  this.toplevel.add(text);
563  }
564 
565  text = new THREE.Mesh(text3d, textMaterial);
566  text.position.set(grminx - offset, grmaxy + offset, textz);
567  text.rotation.x = 0.5*Math.PI;
568  text.rotation.y = -0.25*Math.PI;
569  text.name = "Z axis";
570  this.toplevel.add(text);
571  }
572  if (bothsides) {
573  tick = new THREE.Line(geometry, lineMaterial);
574  tick.position.set(grmaxx,grmaxy,grz);
575  tick.scale.set(plen,plen,1);
576  tick.rotation.z = -Math.PI/2;
577  tick.name = "Z axis " + this.z_handle.format(zticks.tick);
578  this.toplevel.add(tick);
579 
580  tick = new THREE.Line(geometry, lineMaterial);
581  tick.position.set(grmaxx,grminy,grz);
582  tick.scale.set(plen,plen,1);
583  tick.rotation.z = Math.PI;
584  tick.name = "Z axis " + this.z_handle.format(zticks.tick);
585  this.toplevel.add(tick);
586 
587  tick = new THREE.Line(geometry, lineMaterial);
588  tick.position.set(grminx,grminy,grz);
589  tick.scale.set(plen,plen,1);
590  tick.rotation.z = Math.PI/2;
591  tick.name = "Z axis " + this.z_handle.format(zticks.tick);
592  this.toplevel.add(tick);
593  }
594 
595  tick = new THREE.Line(geometry, lineMaterial);
596  tick.position.set(grminx,grmaxy,grz);
597  tick.scale.set(plen,plen,1);
598  tick.name = "Z axis " + this.z_handle.format(zticks.tick);
599  this.toplevel.add(tick);
600  }
601 
602  // for TAxis3D do not show final cube
603  if (this.size3d === 0) return;
604 
605  var wireMaterial = new THREE.MeshBasicMaterial({
606  color : 0x000000,
607  wireframe : true,
608  wireframeLinewidth : 0.5,
609  side : THREE.DoubleSide
610  });
611 
612 
613  // create a new mesh with cube geometry
614  var cube = new THREE.Mesh(new THREE.BoxGeometry(this.size3d * 2, this.size3d * 2, this.size3d * 2), wireMaterial);
615  //cube.position.y = size;
616 
617  var helper = new THREE.BoxHelper(cube);
618  helper.material.color.set(0x000000);
619 
620  var box = new THREE.Object3D();
621  box.add(helper);
622  box.position.z = this.size3d;
623 
624  // add the cube to the scene
625  this.toplevel.add(box);
626  }
627 
628  JSROOT.Painter.TH2Painter_Draw3DBins = function() {
629 
630  var fcolor = d3.rgb(JSROOT.Painter.root_colors[this.GetObject().fFillColor]);
631 
632  var local_bins = this.CreateDrawBins(100, 100);
633 
634  // create the bin cubes
635  var fillcolor = new THREE.Color(0xDDDDDD);
636  fillcolor.setRGB(fcolor.r / 255, fcolor.g / 255, fcolor.b / 255);
637 
638  var material = new THREE.MeshLambertMaterial({ color : fillcolor.getHex() });
639 
640  var geom = new THREE.BoxGeometry(1, 1, 1);
641 
642  var zmin = this.tz.domain()[0], zmax = this.tz.domain()[1];
643 
644  var z1 = this.tz(zmin);
645 
646  for (var i = 0; i < local_bins.length; ++i) {
647  var hh = local_bins[i];
648  if (hh.z <= zmin) continue;
649 
650  var x1 = this.tx(hh.x1), x2 = this.tx(hh.x2),
651  y1 = this.ty(hh.y1), y2 = this.ty(hh.y2),
652  z2 = (hh.z > zmax) ? this.tz(zmax) : this.tz(hh.z);
653 
654  if ((x1 < -1.001*this.size3d) || (x2 > 1.001*this.size3d) ||
655  (y1 < -1.001*this.size3d) || (y2 > 1.001*this.size3d)) continue;
656 
657  // create a new mesh with cube geometry
658  var bin = new THREE.Mesh(geom, material.clone());
659 
660  bin.position.set((x1+x2)/2, (y1+y2)/2, (z1+z2)/2);
661  bin.scale.set(x2-x1,y2-y1,z2-z1);
662 
663  if ('tip' in hh) bin.name = hh.tip;
664  this.toplevel.add(bin);
665 
666  var helper = new THREE.BoxHelper(bin);
667  helper.material.color.set(0x000000);
668  helper.material.linewidth = 1.0;
669  this.toplevel.add(helper);
670  }
671 
672  delete local_bins;
673  local_bins = null;
674  }
675 
676  JSROOT.Painter.Render3D = function(tmout) {
677  if (tmout === undefined) tmout = 5; // by default, rendering happens with timeout
678 
679  if (tmout <= 0) {
680  if ('render_tmout' in this)
681  clearTimeout(this.render_tmout);
682 
683  if (this.renderer === undefined) return;
684 
685  var tm1 = new Date();
686 
687  // do rendering, most consuming time
688  this.renderer.render(this.scene, this.camera);
689 
690  var tm2 = new Date();
691 
692  delete this.render_tmout;
693 
694  if (this.first_render_tm === 0) {
695  this.first_render_tm = tm2.getTime() - tm1.getTime();
696  console.log('First render tm = ' + this.first_render_tm);
697  this['Add3DInteraction'] = JSROOT.Painter.add3DInteraction;
698  this.Add3DInteraction();
699  }
700 
701  return;
702  }
703 
704  // no need to shoot rendering once again
705  if ('render_tmout' in this) return;
706 
707  this.render_tmout = setTimeout(this.Render3D.bind(this,0), tmout);
708  }
709 
710 
711  JSROOT.Painter.Resize3D = function() {
712 
713  var size3d = this.size_for_3d(this.svg_pad().property('can3d'));
714 
715  this.apply_3d_size(size3d);
716 
717  if ((this.scene_width === size3d.width) && (this.scene_height === size3d.height)) return;
718 
719  if ((size3d.width<10) || (size3d.height<10)) return;
720 
721  this.scene_width = size3d.width;
722  this.scene_height = size3d.height;
723 
724  this.camera.aspect = this.scene_width / this.scene_height;
725  this.camera.updateProjectionMatrix();
726 
727  this.renderer.setSize( this.scene_width, this.scene_height );
728 
729  this.Render3D();
730  }
731 
732  JSROOT.Painter.TH2Painter_Draw3D = function(call_back) {
733  // function called with this as painter
734 
735  this.Create3DScene();
736 
737  this.zmin = this.options.Logz ? this.gmin0bin * 0.3 : this.gminbin;
738  this.zmax = this.gmaxbin * 1.05; // not very nice
739 
740  this.DrawXYZ();
741 
742  this.Draw3DBins();
743 
744  this.DrawTitle();
745 
746  this.Render3D();
747 
748  JSROOT.CallBack(call_back);
749  }
750 
751  // ==============================================================================
752 
753 
754  JSROOT.TH3Painter = function(histo) {
755  JSROOT.THistPainter.call(this, histo);
756 
757  this['Create3DScene'] = JSROOT.Painter.HPainter_Create3DScene;
758  }
759 
760  JSROOT.TH3Painter.prototype = Object.create(JSROOT.THistPainter.prototype);
761 
762  JSROOT.TH3Painter.prototype.ScanContent = function() {
763  var histo = this.GetObject();
764 
765  this.nbinsx = histo.fXaxis.fNbins;
766  this.nbinsy = histo.fYaxis.fNbins;
767  this.nbinsz = histo.fZaxis.fNbins;
768 
769  this.xmin = histo.fXaxis.fXmin;
770  this.xmax = histo.fXaxis.fXmax;
771 
772  this.ymin = histo.fYaxis.fXmin;
773  this.ymax = histo.fYaxis.fXmax;
774 
775  this.zmin = histo.fZaxis.fXmin;
776  this.zmax = histo.fZaxis.fXmax;
777 
778  // global min/max, used at the moment in 3D drawing
779 
780  this.gminbin = this.gmaxbin = histo.getBinContent(1,1,1);
781  var i,j,k;
782  for (i = 0; i < this.nbinsx; ++i)
783  for (j = 0; j < this.nbinsy; ++j)
784  for (k = 0; k < this.nbinsz; ++k) {
785  var bin_content = histo.getBinContent(i+1, j+1, k+1);
786  if (bin_content < this.gminbin) this.gminbin = bin_content; else
787  if (bin_content > this.gmaxbin) this.gmaxbin = bin_content;
788  }
789 
790  this.draw_content = this.gmaxbin > 0;
791 
792  this.CreateAxisFuncs(true, true);
793  }
794 
795  JSROOT.TH3Painter.prototype.CountStat = function() {
796  var histo = this.GetObject(),
797  stat_sum0 = 0, stat_sumx1 = 0, stat_sumy1 = 0,
798  stat_sumz1 = 0, stat_sumx2 = 0, stat_sumy2 = 0, stat_sumz2 = 0,
799  i1 = this.GetSelectIndex("x", "left"),
800  i2 = this.GetSelectIndex("x", "right"),
801  j1 = this.GetSelectIndex("y", "left"),
802  j2 = this.GetSelectIndex("y", "right"),
803  k1 = this.GetSelectIndex("z", "left"),
804  k2 = this.GetSelectIndex("z", "right"),
805  res = { entries: 0, integral: 0, meanx: 0, meany: 0, meanz: 0, rmsx: 0, rmsy: 0, rmsz: 0 };
806 
807  for (var xi = 0; xi < this.nbinsx+2; ++xi) {
808 
809  var xx = this.GetBinX(xi - 0.5);
810  var xside = (xi < i1) ? 0 : (xi > i2 ? 2 : 1);
811 
812  for (var yi = 0; yi < this.nbinsy+2; ++yi) {
813 
814  var yy = this.GetBinY(yi - 0.5);
815  var yside = (yi < j1) ? 0 : (yi > j2 ? 2 : 1);
816 
817  for (var zi = 0; zi < this.nbinsz+2; ++zi) {
818 
819  var zz = this.GetBinZ(zi - 0.5);
820  var zside = (zi < k1) ? 0 : (zi > k2 ? 2 : 1);
821 
822  var cont = histo.getBinContent(xi, yi, zi);
823  res.entries += cont;
824 
825  if ((xside==1) && (yside==1) && (zside==1)) {
826  stat_sum0 += cont;
827  stat_sumx1 += xx * cont;
828  stat_sumy1 += yy * cont;
829  stat_sumz1 += zz * cont;
830  stat_sumx2 += xx * xx * cont;
831  stat_sumy2 += yy * yy * cont;
832  stat_sumz2 += zz * zz * cont;
833  }
834  }
835  }
836  }
837 
838  if (histo.fTsumw > 0) {
839  stat_sum0 = histo.fTsumw;
840  stat_sumx1 = histo.fTsumwx;
841  stat_sumx2 = histo.fTsumwx2;
842  stat_sumy1 = histo.fTsumwy;
843  stat_sumy2 = histo.fTsumwy2;
844  stat_sumz1 = histo.fTsumwz;
845  stat_sumz2 = histo.fTsumwz2;
846  }
847 
848  if (stat_sum0 > 0) {
849  res.meanx = stat_sumx1 / stat_sum0;
850  res.meany = stat_sumy1 / stat_sum0;
851  res.meanz = stat_sumz1 / stat_sum0;
852  res.rmsx = Math.sqrt(stat_sumx2 / stat_sum0 - res.meanx * res.meanx);
853  res.rmsy = Math.sqrt(stat_sumy2 / stat_sum0 - res.meany * res.meany);
854  res.rmsz = Math.sqrt(stat_sumz2 / stat_sum0 - res.meanz * res.meanz);
855  }
856 
857  res.integral = stat_sum0;
858 
859  if (histo.fEntries > 1) res.entries = histo.fEntries;
860 
861  return res;
862  }
863 
864  JSROOT.TH3Painter.prototype.FillStatistic = function(stat, dostat, dofit) {
865  if (this.GetObject()===null) return false;
866 
867  var pave = stat.GetObject(),
868  data = this.CountStat(),
869  print_name = dostat % 10,
870  print_entries = Math.floor(dostat / 10) % 10,
871  print_mean = Math.floor(dostat / 100) % 10,
872  print_rms = Math.floor(dostat / 1000) % 10,
873  print_under = Math.floor(dostat / 10000) % 10,
874  print_over = Math.floor(dostat / 100000) % 10,
875  print_integral = Math.floor(dostat / 1000000) % 10;
876  //var print_skew = Math.floor(dostat / 10000000) % 10;
877  //var print_kurt = Math.floor(dostat / 100000000) % 10;
878 
879  if (print_name > 0)
880  pave.AddText(this.GetObject().fName);
881 
882  if (print_entries > 0)
883  pave.AddText("Entries = " + stat.Format(data.entries,"entries"));
884 
885  if (print_mean > 0) {
886  pave.AddText("Mean x = " + stat.Format(data.meanx));
887  pave.AddText("Mean y = " + stat.Format(data.meany));
888  pave.AddText("Mean z = " + stat.Format(data.meanz));
889  }
890 
891  if (print_rms > 0) {
892  pave.AddText("Std Dev x = " + stat.Format(data.rmsx));
893  pave.AddText("Std Dev y = " + stat.Format(data.rmsy));
894  pave.AddText("Std Dev z = " + stat.Format(data.rmsz));
895  }
896 
897  if (print_integral > 0) {
898  pave.AddText("Integral = " + stat.Format(data.integral,"entries"));
899  }
900 
901  // adjust the size of the stats box with the number of lines
902 
903  var nlines = pave.fLines.arr.length,
904  stath = nlines * JSROOT.gStyle.StatFontSize;
905  if (stath <= 0 || 3 == (JSROOT.gStyle.StatFont % 10)) {
906  stath = 0.25 * nlines * JSROOT.gStyle.StatH;
907  pave.fY1NDC = 0.93 - stath;
908  pave.fY2NDC = 0.93;
909  }
910 
911  return true;
912  }
913 
914  JSROOT.TH3Painter.prototype.Draw3DBins = function() {
915 
916  if (!this.draw_content) return;
917 
918  var fcolor = d3.rgb(JSROOT.Painter.root_colors[this.GetObject().fFillColor]);
919 
920  var fillcolor = new THREE.Color(0xDDDDDD);
921  fillcolor.setRGB(fcolor.r / 255, fcolor.g / 255, fcolor.b / 255);
922 
923  var material = null, geom = null;
924 
925  if (this.options.Box == 11) {
926  material = new THREE.MeshPhongMaterial({ color : fillcolor.getHex(), specular : 0x4f4f4f });
927  //geom = new THREE.SphereGeometry(this.size3d / this.nbinsx);
928  geom = new THREE.SphereGeometry(0.5);
929  geom.applyMatrix( new THREE.Matrix4().makeRotationX( Math.PI / 2 ) );
930  //geom.scale(1, this.nbinsx / this.nbinsy, this.nbinsx / this.nbinsz);
931  } else {
932  material = new THREE.MeshLambertMaterial({ color : fillcolor.getHex() });
933  // geom = new THREE.BoxGeometry(2 * this.size3d / this.nbinsx, 2 * this.size3d / this.nbinsy, 2 * this.size3d / this.nbinsz);
934  geom = new THREE.BoxGeometry(1, 1, 1);
935  }
936 
937  var histo = this.GetObject(),
938  i1 = this.GetSelectIndex("x", "left", 0),
939  i2 = this.GetSelectIndex("x", "right", 0),
940  j1 = this.GetSelectIndex("y", "left", 0),
941  j2 = this.GetSelectIndex("y", "right", 0),
942  k1 = this.GetSelectIndex("z", "left", 0),
943  k2 = this.GetSelectIndex("z", "right", 0),
944  name = this.GetTipName("<br/>");
945 
946  var scalex = (this.tx(this.GetBinX(i2+0.5)) - this.tx(this.GetBinX(i1+0.5))) / (i2-i1),
947  scaley = (this.ty(this.GetBinY(j2+0.5)) - this.ty(this.GetBinY(j1+0.5))) / (j2-j1),
948  scalez = (this.tz(this.GetBinZ(k2+0.5)) - this.tz(this.GetBinZ(k1+0.5))) / (k2-k1);
949 
950  for (var i = i1; i < i2; ++i) {
951  var binx = this.GetBinX(i+0.5), grx = this.tx(binx);
952  for (var j = j1; j < j2; ++j) {
953  var biny = this.GetBinY(j+0.5), gry = this.ty(biny);
954  for (var k = k1; k < k2; ++k) {
955  var bin_content = histo.getBinContent(i+1, j+1, k+1);
956  if (bin_content <= this.gminbin) continue;
957 
958  var wei = (this.options.Color > 0) ? 1. : bin_content / this.gmaxbin;
959 
960  if (wei < 1e-5) continue; // do not show empty bins
961 
962  var binz = this.GetBinZ(k+0.5), grz = this.tz(binz);
963 
964  var bin = new THREE.Mesh(geom, material.clone());
965 
966  bin.position.set( grx, gry, grz );
967 
968  bin.scale.set(scalex*wei, scaley*wei, scalez*wei);
969 
970  if (JSROOT.gStyle.Tooltip > 0)
971  bin.name = name + 'x=' + JSROOT.FFormat(binx,"6.4g") + ' bin=' + (i+1) + '<br/>'
972  + 'y=' + JSROOT.FFormat(biny,"6.4g") + ' bin=' + (j+1) + '<br/>'
973  + 'z=' + JSROOT.FFormat(binz,"6.4g") + ' bin=' + (k+1) + '<br/>'
974  + 'entries=' + JSROOT.FFormat(bin_content, "7.0g");
975 
976  this.toplevel.add(bin);
977 
978  if (this.options.Box !== 11) {
979  var helper = new THREE.BoxHelper(bin);
980  helper.material.color.set(0x000000);
981  helper.material.linewidth = 1.0;
982  this.toplevel.add(helper)
983  }
984  }
985  }
986  }
987  }
988 
989  JSROOT.TH3Painter.prototype.Redraw = function(resize) {
990  if (resize) {
991  this.Resize3D();
992  } else {
993  this.Create3DScene();
994  this.DrawXYZ();
995  this.Draw3DBins();
996  this.Render3D();
997  }
998  }
999 
1000  JSROOT.TH3Painter.prototype.CheckResize = function(size) {
1001  var pad_painter = this.pad_painter();
1002 
1003  var changed = true;
1004 
1005  // firefox is the only browser which correctly supports resize of embedded canvas,
1006  // for others we should force canvas redrawing at every step
1007  if (pad_painter)
1008  changed = pad_painter.CheckCanvasResize(size, JSROOT.browser.isFirefox ? false : true);
1009 
1010  if (changed) this.Resize3D(size);
1011  }
1012 
1013  JSROOT.TH3Painter.prototype.FillToolbar = function() {
1014  var pp = this.pad_painter(true);
1015  if (pp===null) return;
1016 
1017  pp.AddButton(JSROOT.ToolbarIcons.undo, 'Unzoom all axes', 'UnzoomAllAxis');
1018  if (this.draw_content)
1019  pp.AddButton(JSROOT.ToolbarIcons.statbox, 'Toggle stat box', "ToggleStatBox");
1020  }
1021 
1022  JSROOT.TH3Painter.prototype.FillHistContextMenu = function(menu) {
1023  if (!this.draw_content) return;
1024 
1025  menu.addDrawMenu("Draw with", ["box", "box1"], function(arg) {
1026  this.options = this.DecodeOptions(arg);
1027  this.Redraw();
1028  });
1029  }
1030 
1031 
1032  JSROOT.Painter.drawHistogram3D = function(divid, histo, opt) {
1033  // when called, *this* set to painter instance
1034 
1035  // create painter and add it to canvas
1036  JSROOT.extend(this, new JSROOT.TH3Painter(histo));
1037 
1038  this.SetDivId(divid, 4);
1039 
1040  this.options = this.DecodeOptions(opt);
1041 
1042  this.CheckPadOptions();
1043 
1044  this.ScanContent();
1045 
1046  this.Redraw();
1047 
1048  this.DrawTitle();
1049 
1050  if (JSROOT.gStyle.AutoStat && this.create_canvas) {
1051  var stats = this.CreateStat();
1052  if (stats) JSROOT.draw(this.divid, stats, "");
1053  }
1054 
1055  this.FillToolbar();
1056 
1057  return this.DrawingReady();
1058  }
1059 
1060  // ===================================================================
1061 
1062  JSROOT.Painter.drawPolyMarker3D = function(divid, poly, opt) {
1063  // when called, *this* set to painter instance
1064 
1065  this.SetDivId(divid);
1066 
1067  var main = this.main_painter();
1068 
1069  if ((main == null) || !('renderer' in main)) return this.DrawingReady();
1070 
1071  var cnt = poly.fP.length;
1072  var step = 3;
1073 
1074  if ((JSROOT.gStyle.OptimizeDraw > 0) && (cnt > 1000*3)) {
1075  step = Math.floor(cnt / 1000 / 3) * 3;
1076  if (step <= 6) step = 6;
1077  }
1078 
1079  var fcolor = d3.rgb(JSROOT.Painter.root_colors[poly.fMarkerColor]);
1080  var fillcolor = new THREE.Color(0xDDDDDD);
1081  fillcolor.setRGB(fcolor.r / 255, fcolor.g / 255, fcolor.b / 255);
1082 
1083  var material = new THREE.MeshPhongMaterial({ color : fillcolor.getHex(), specular : 0x4f4f4f});
1084 
1085  // var geom = new THREE.SphereBufferGeometry(1);
1086  var geom = new THREE.BoxGeometry(1, 1, 1);
1087 
1088  for (var n=0; n<cnt; n+=step) {
1089  var bin = new THREE.Mesh(geom, material.clone());
1090  bin.position.set( main.tx(poly.fP[n]), main.ty(poly.fP[n+1]), main.tz(poly.fP[n+2]) );
1091  bin.name = (poly.fName !== "TPolyMarker3D") ? (poly.fName + ": ") : ("bin " + n/3 + ": ");
1092  bin.name += main.x_handle.format(poly.fP[n]) + "," + main.y_handle.format(poly.fP[n+1]) + "," + main.z_handle.format(poly.fP[n+2]);
1093  main.toplevel.add(bin);
1094  }
1095 
1096  main.Render3D();
1097 
1098  return this.DrawingReady();
1099  }
1100 
1101  return JSROOT.Painter;
1102 
1103 }));
1104