otsdaq_utilities  v2_05_02_indev
JSRootPainter.hist3d.js
1 
4 (function( factory ) {
5  if ( typeof define === "function" && define.amd ) {
6  define( [ 'JSRootCore', 'd3', 'JSRootPainter.hist', '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("./JSRootPainter.hist.js"), 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', 'JSRootPainter.hist3d.js');
14  if (typeof d3 != 'object')
15  throw new Error('This extension requires d3.js', 'JSRootPainter.hist3d.js');
16  if (typeof THREE == 'undefined')
17  throw new Error('THREE is not defined', 'JSRoot3DPainter.js');
18  factory(JSROOT, d3, JSROOT, THREE, THREE);
19  }
20 } (function(JSROOT, d3, __DUMMY__, THREE, THREE_MORE, document) {
21 
22  "use strict";
23 
24  JSROOT.sources.push("hist3d");
25 
26  if ((typeof document=='undefined') && (typeof window=='object')) document = window.document;
27 
28  if (typeof JSROOT.THistPainter === 'undefined')
29  throw new Error('JSROOT.THistPainter is not defined', 'JSRootPainter.hist3d.js');
30 
31  JSROOT.TFramePainter.prototype.SetCameraPosition = function(pad, first_time) {
32  var max3d = Math.max(0.75*this.size_xy3d, this.size_z3d);
33 
34  if (first_time)
35  this.camera.position.set(-1.6*max3d, -3.5*max3d, 1.4*this.size_z3d);
36 
37  if (pad && (first_time || !this.zoom_changed_interactive))
38  if (!isNaN(pad.fTheta) && !isNaN(pad.fPhi) && ((pad.fTheta !== this.camera_Theta) || (pad.fPhi !== this.camera_Phi))) {
39  max3d = 3*Math.max(this.size_xy3d, this.size_z3d);
40  var phi = (-pad.fPhi-90)/180*Math.PI, theta = pad.fTheta/180*Math.PI;
41 
42  this.camera_Phi = pad.fPhi;
43  this.camera_Theta = pad.fTheta;
44 
45  this.camera.position.set(max3d*Math.cos(phi)*Math.cos(theta),
46  max3d*Math.sin(phi)*Math.cos(theta),
47  this.size_z3d + max3d*Math.sin(theta));
48 
49  first_time = true;
50  }
51 
52  if (first_time)
53  this.camera.lookAt(this.lookat);
54  }
55 
57  JSROOT.TFramePainter.prototype.Create3DScene = function(arg) {
58 
59  if ((arg!==undefined) && (arg<0)) {
60 
61  if (!this.mode3d) return;
62 
63  //if (typeof this.TestAxisVisibility === 'function')
64  this.TestAxisVisibility(null, this.toplevel);
65 
66  this.clear_3d_canvas();
67 
68  JSROOT.Painter.DisposeThreejsObject(this.scene);
69  if (this.control) this.control.Cleanup();
70 
71  if (this.renderer) {
72  if (this.renderer.dispose) this.renderer.dispose();
73  if (this.renderer.context) delete this.renderer.context;
74  }
75 
76  delete this.size_xy3d;
77  delete this.size_z3d;
78  delete this.tooltip_mesh;
79  delete this.scene;
80  delete this.toplevel;
81  delete this.camera;
82  delete this.pointLight;
83  delete this.renderer;
84  delete this.control;
85  if ('render_tmout' in this) {
86  clearTimeout(this.render_tmout);
87  delete this.render_tmout;
88  }
89 
90  this.mode3d = false;
91 
92  return;
93  }
94 
95  this.mode3d = true; // indicate 3d mode as hist painter does
96 
97  if ('toplevel' in this) {
98  // it is indication that all 3D object created, just replace it with empty
99  this.scene.remove(this.toplevel);
100  JSROOT.Painter.DisposeThreejsObject(this.toplevel);
101  delete this.tooltip_mesh;
102  delete this.toplevel;
103  if (this.control) this.control.HideTooltip();
104 
105  var newtop = new THREE.Object3D();
106  this.scene.add(newtop);
107  this.toplevel = newtop;
108 
109  this.Resize3D(); // set actual sizes
110 
111  this.SetCameraPosition(this.root_pad(), false);
112 
113  return;
114  }
115 
116  this.usesvg = JSROOT.Painter.UseSVGFor3D(); // SVG used in batch mode
117  //if (this.usesvg) this.usesvgimg = JSROOT.gStyle.ImageSVG; // use svg images to insert graphics
118 
119  var sz = this.size_for_3d(this.usesvg ? 3 : undefined);
120 
121  this.size_z3d = 100;
122  this.size_xy3d = (sz.height > 10) && (sz.width > 10) ? Math.round(sz.width/sz.height*this.size_z3d) : this.size_z3d;
123 
124  // three.js 3D drawing
125  this.scene = new THREE.Scene();
126  //scene.fog = new THREE.Fog(0xffffff, 500, 3000);
127 
128  this.toplevel = new THREE.Object3D();
129  this.scene.add(this.toplevel);
130  this.scene_width = sz.width;
131  this.scene_height = sz.height;
132 
133  this.camera = new THREE.PerspectiveCamera(45, this.scene_width / this.scene_height, 1, 40*this.size_z3d);
134 
135  this.camera_Phi = 30;
136  this.camera_Theta = 30;
137 
138  this.pointLight = new THREE.PointLight(0xffffff,1);
139  this.camera.add(this.pointLight);
140  this.pointLight.position.set(this.size_xy3d/2, this.size_xy3d/2, this.size_z3d/2);
141  this.lookat = new THREE.Vector3(0,0,0.8*this.size_z3d);
142  this.camera.up = new THREE.Vector3(0,0,1);
143  this.scene.add( this.camera );
144 
145  this.SetCameraPosition(this.root_pad(), true);
146 
147  var res = JSROOT.Painter.Create3DRenderer(this.scene_width, this.scene_height, this.usesvg, (sz.can3d == 4));
148 
149  this.renderer = res.renderer;
150  this.webgl = res.usewebgl;
151  this.add_3d_canvas(sz, res.dom);
152 
153  this.first_render_tm = 0;
154  this.enable_highlight = false;
155 
156  if (JSROOT.BatchMode) return;
157 
158  this.control = JSROOT.Painter.CreateOrbitControl(this, this.camera, this.scene, this.renderer, this.lookat);
159 
160  var axis_painter = this, obj_painter = this.main_painter();
161 
162  this.control.ProcessMouseMove = function(intersects) {
163  var tip = null, mesh = null, zoom_mesh = null;
164 
165  for (var i = 0; i < intersects.length; ++i) {
166  if (intersects[i].object.tooltip) {
167  tip = intersects[i].object.tooltip(intersects[i]);
168  if (tip) { mesh = intersects[i].object; break; }
169  } else if (intersects[i].object.zoom && !zoom_mesh) {
170  zoom_mesh = intersects[i].object;
171  }
172  }
173 
174  if (tip && !tip.use_itself) {
175  var delta_xy = 1e-4*axis_painter.size_xy3d, delta_z = 1e-4*axis_painter.size_z3d;
176  if ((tip.x1 > tip.x2) || (tip.y1 > tip.y2) || (tip.z1 > tip.z2)) console.warn('check 3D hints coordinates');
177  tip.x1 -= delta_xy; tip.x2 += delta_xy;
178  tip.y1 -= delta_xy; tip.y2 += delta_xy;
179  tip.z1 -= delta_z; tip.z2 += delta_z;
180  }
181 
182  axis_painter.BinHighlight3D(tip, mesh);
183 
184  if (!tip && zoom_mesh && axis_painter.Get3DZoomCoord) {
185  var pnt = zoom_mesh.GlobalIntersect(this.raycaster),
186  axis_name = zoom_mesh.zoom,
187  axis_value = axis_painter.Get3DZoomCoord(pnt, axis_name);
188 
189  if ((axis_name==="z") && zoom_mesh.use_y_for_z) axis_name = "y";
190 
191  var taxis = axis_painter.GetAxis(axis_name);
192 
193  var hint = { name: axis_name,
194  title: "TAxis",
195  line: "any info",
196  only_status: true };
197 
198  if (taxis) { hint.name = taxis.fName; hint.title = taxis.fTitle || "histogram TAxis object"; }
199 
200  hint.line = axis_name + " : " + axis_painter.AxisAsText(axis_name, axis_value);
201 
202  return hint;
203  }
204 
205  return (tip && tip.lines) ? tip : "";
206  }
207 
208  this.control.ProcessMouseLeave = function() {
209  axis_painter.BinHighlight3D(null);
210  }
211 
212  this.control.ContextMenu = function(pos, intersects) {
213  var kind = "hist", p = obj_painter;
214  if (intersects)
215  for (var n=0;n<intersects.length;++n) {
216  var mesh = intersects[n].object;
217  if (mesh.zoom) { kind = mesh.zoom; break; }
218  if (mesh.painter && typeof mesh.painter.ShowContextMenu === 'function') {
219  p = mesh.painter; break;
220  }
221  }
222 
223  p.ShowContextMenu(kind, pos);
224  }
225  }
226 
230  JSROOT.TFramePainter.prototype.SetActive = function(on) {
231  if (this.control)
232  this.control.enableKeys = on;
233  }
234 
245  JSROOT.TFramePainter.prototype.Render3D = function(tmout) {
246 
247  if (tmout === -1111) {
248  // special handling for direct SVG renderer
249  // probably, here one can use canvas renderer - after modifications
250  // var rrr = new THREE.SVGRenderer({ precision: 0, astext: true });
251  var rrr = THREE.CreateSVGRenderer(false, 0, document);
252  rrr.setSize(this.scene_width, this.scene_height);
253  rrr.render(this.scene, this.camera);
254  if (rrr.makeOuterHTML) {
255  // use text mode, it is faster
256  var d = document.createElement('div');
257  d.innerHTML = rrr.makeOuterHTML();
258  return d.childNodes[0];
259  }
260  return rrr.domElement;
261  }
262 
263  if (tmout === undefined) tmout = 5; // by default, rendering happens with timeout
264 
265  if ((tmout <= 0) || this.usesvg || JSROOT.BatchMode) {
266  if ('render_tmout' in this) {
267  clearTimeout(this.render_tmout);
268  } else {
269  if (tmout === -2222) return; // special case to check if rendering timeout was active
270  }
271 
272  if (this.renderer === undefined) return;
273 
274  var tm1 = new Date();
275 
276  if (!this.opt3d) this.opt3d = { FrontBox: true, BackBox: true };
277 
278  //if (typeof this.TestAxisVisibility === 'function')
279  this.TestAxisVisibility(this.camera, this.toplevel, this.opt3d.FrontBox, this.opt3d.BackBox);
280 
281  // do rendering, most consuming time
282  this.renderer.render(this.scene, this.camera);
283 
284  // no idea why - SoftwareRenderer requires second call
285  if ((this.first_render_tm === 0) && (this.renderer instanceof THREE.SoftwareRenderer))
286  this.renderer.render(this.scene, this.camera);
287 
288  JSROOT.Painter.AfterRender3D(this.renderer);
289 
290  var tm2 = new Date();
291 
292  delete this.render_tmout;
293 
294  if (this.first_render_tm === 0) {
295  this.first_render_tm = tm2.getTime() - tm1.getTime();
296  this.enable_highlight = (this.first_render_tm < 1200) && this.IsTooltipAllowed();
297  console.log('First render tm = ' + this.first_render_tm);
298  }
299 
300  return;
301  }
302 
303  // no need to shoot rendering once again
304  if ('render_tmout' in this) return;
305 
306  this.render_tmout = setTimeout(this.Render3D.bind(this,0), tmout);
307  }
308 
309  JSROOT.TFramePainter.prototype.Resize3D = function() {
310 
311  var sz = this.size_for_3d(this.access_3d_kind());
312 
313  this.apply_3d_size(sz);
314 
315  if ((this.scene_width === sz.width) && (this.scene_height === sz.height)) return false;
316 
317  if ((sz.width<10) || (sz.height<10)) return false;
318 
319  // TODO: change xy/z ratio after canvas resize
320  // this.size_xy3d = Math.round(sz.width/sz.height*this.size_z3d);
321 
322  this.scene_width = sz.width;
323  this.scene_height = sz.height;
324 
325  this.camera.aspect = this.scene_width / this.scene_height;
326  this.camera.updateProjectionMatrix();
327 
328  this.renderer.setSize( this.scene_width, this.scene_height );
329 
330  return true;
331  }
332 
333  JSROOT.TFramePainter.prototype.BinHighlight3D = function(tip, selfmesh) {
334 
335  var changed = false, tooltip_mesh = null, changed_self = true,
336  want_remove = !tip || (tip.x1===undefined) || !this.enable_highlight;
337 
338  if (this.tooltip_selfmesh) {
339  changed_self = (this.tooltip_selfmesh !== selfmesh)
340  this.tooltip_selfmesh.material.color = this.tooltip_selfmesh.save_color;
341  delete this.tooltip_selfmesh;
342  changed = true;
343  }
344 
345  if (this.tooltip_mesh) {
346  tooltip_mesh = this.tooltip_mesh;
347  this.toplevel.remove(this.tooltip_mesh);
348  delete this.tooltip_mesh;
349  changed = true;
350  }
351 
352  if (want_remove) {
353  if (changed) this.Render3D();
354  this.ProvideUserTooltip(null);
355  return;
356  }
357 
358  if (tip.use_itself) {
359  selfmesh.save_color = selfmesh.material.color;
360  selfmesh.material.color = new THREE.Color(tip.color);
361  this.tooltip_selfmesh = selfmesh;
362  changed = changed_self;
363  } else {
364  changed = true;
365 
366  var indicies = JSROOT.Painter.Box3D.Indexes,
367  normals = JSROOT.Painter.Box3D.Normals,
368  vertices = JSROOT.Painter.Box3D.Vertices,
369  pos, norm,
370  color = new THREE.Color(tip.color ? tip.color : 0xFF0000),
371  opacity = tip.opacity || 1;
372 
373  if (!tooltip_mesh) {
374  pos = new Float32Array(indicies.length*3);
375  norm = new Float32Array(indicies.length*3);
376  var geom = new THREE.BufferGeometry();
377  geom.addAttribute( 'position', new THREE.BufferAttribute( pos, 3 ) );
378  geom.addAttribute( 'normal', new THREE.BufferAttribute( norm, 3 ) );
379  var mater = new THREE.MeshBasicMaterial( { color: color, opacity: opacity, flatShading: true } );
380  tooltip_mesh = new THREE.Mesh(geom, mater);
381  } else {
382  pos = tooltip_mesh.geometry.attributes.position.array;
383  tooltip_mesh.geometry.attributes.position.needsUpdate = true;
384  tooltip_mesh.material.color = color;
385  tooltip_mesh.material.opacity = opacity;
386  }
387 
388  if (tip.x1 === tip.x2) console.warn('same tip X', tip.x1, tip.x2);
389  if (tip.y1 === tip.y2) console.warn('same tip Y', tip.y1, tip.y2);
390  if (tip.z1 === tip.z2) { tip.z2 = tip.z1 + 0.0001; } // avoid zero faces
391 
392  for (var k=0,nn=-3;k<indicies.length;++k) {
393  var vert = vertices[indicies[k]];
394  pos[k*3] = tip.x1 + vert.x * (tip.x2 - tip.x1);
395  pos[k*3+1] = tip.y1 + vert.y * (tip.y2 - tip.y1);
396  pos[k*3+2] = tip.z1 + vert.z * (tip.z2 - tip.z1);
397 
398  if (norm) {
399  if (k%6===0) nn+=3;
400  norm[k*3] = normals[nn];
401  norm[k*3+1] = normals[nn+1];
402  norm[k*3+2] = normals[nn+2];
403  }
404  }
405  this.tooltip_mesh = tooltip_mesh;
406  this.toplevel.add(tooltip_mesh);
407  }
408 
409  if (changed) this.Render3D();
410 
411  if (changed && tip.$painter && (typeof tip.$painter.RedrawProjection == 'function'))
412  tip.$painter.RedrawProjection(tip.ix-1, tip.ix, tip.iy-1, tip.iy);
413 
414  if (this.GetObject())
415  this.ProvideUserTooltip({ obj: this.GetObject(), name: this.GetObject().fName,
416  bin: tip.bin, cont: tip.value,
417  binx: tip.ix, biny: tip.iy, binz: tip.iz,
418  grx: (tip.x1+tip.x2)/2, gry: (tip.y1+tip.y2)/2, grz: (tip.z1+tip.z2)/2 });
419  }
420 
421  JSROOT.TFramePainter.prototype.TestAxisVisibility = function(camera, toplevel, fb, bb) {
422  var top;
423  if (toplevel && toplevel.children)
424  for (var n=0;n<toplevel.children.length;++n) {
425  top = toplevel.children[n];
426  if (top.axis_draw) break;
427  top = undefined;
428  }
429 
430  if (!top) return;
431 
432  if (!camera) {
433  // this is case when axis drawing want to be removed
434  toplevel.remove(top);
435  // delete this.TestAxisVisibility;
436  return;
437  }
438 
439  fb = fb ? true : false;
440  bb = bb ? true : false;
441 
442  var qudrant = 1, pos = camera.position;
443  if ((pos.x < 0) && (pos.y >= 0)) qudrant = 2;
444  if ((pos.x >= 0) && (pos.y >= 0)) qudrant = 3;
445  if ((pos.x >= 0) && (pos.y < 0)) qudrant = 4;
446 
447  function testvisible(id, range) {
448  if (id <= qudrant) id+=4;
449  return (id > qudrant) && (id < qudrant+range);
450  }
451 
452  for (var n=0;n<top.children.length;++n) {
453  var chld = top.children[n];
454  if (chld.grid) chld.visible = bb && testvisible(chld.grid, 3); else
455  if (chld.zid) chld.visible = testvisible(chld.zid, 2); else
456  if (chld.xyid) chld.visible = testvisible(chld.xyid, 3); else
457  if (chld.xyboxid) {
458  var range = 5, shift = 0;
459  if (bb && !fb) { range = 3; shift = -2; } else
460  if (fb && !bb) range = 3; else
461  if (!fb && !bb) range = (chld.bottom ? 3 : 0);
462  chld.visible = testvisible(chld.xyboxid + shift, range);
463  if (!chld.visible && chld.bottom && bb)
464  chld.visible = testvisible(chld.xyboxid, 3);
465  } else
466  if (chld.zboxid) {
467  var range = 2, shift = 0;
468  if (fb && bb) range = 5; else
469  if (bb && !fb) range = 4; else
470  if (!bb && fb) { shift = -2; range = 4; }
471  chld.visible = testvisible(chld.zboxid + shift, range);
472  }
473  }
474  }
475 
476  JSROOT.TFramePainter.prototype.Set3DOptions = function(hopt) {
477  this.opt3d = hopt;
478  }
479 
480  JSROOT.TFramePainter.prototype.DrawXYZ = function(toplevel, opts) {
481  if (!opts) opts = {};
482 
483  var grminx = -this.size_xy3d, grmaxx = this.size_xy3d,
484  grminy = -this.size_xy3d, grmaxy = this.size_xy3d,
485  grminz = 0, grmaxz = 2*this.size_z3d,
486  textsize = Math.round(this.size_z3d * 0.05),
487  pad = this.root_pad(),
488  xmin = this.xmin, xmax = this.xmax,
489  ymin = this.ymin, ymax = this.ymax,
490  zmin = this.zmin, zmax = this.zmax,
491  y_zoomed = false, z_zoomed = false;
492 
493  if (!this.size_z3d) {
494  grminx = this.xmin; grmaxx = this.xmax;
495  grminy = this.ymin; grmaxy = this.ymax;
496  grminz = this.zmin; grmaxz = this.zmax;
497  textsize = (grmaxz - grminz) * 0.05;
498  }
499 
500  if (('zoom_xmin' in this) && ('zoom_xmax' in this) && (this.zoom_xmin !== this.zoom_xmax)) {
501  xmin = this.zoom_xmin; xmax = this.zoom_xmax;
502  }
503  if (('zoom_ymin' in this) && ('zoom_ymax' in this) && (this.zoom_ymin !== this.zoom_ymax)) {
504  ymin = this.zoom_ymin; ymax = this.zoom_ymax; y_zoomed = true;
505  }
506 
507  if (('zoom_zmin' in this) && ('zoom_zmax' in this) && (this.zoom_zmin !== this.zoom_zmax)) {
508  zmin = this.zoom_zmin; zmax = this.zoom_zmax; z_zoomed = true;
509  }
510 
511  if (opts.use_y_for_z) {
512  this.zmin = this.ymin; this.zmax = this.ymax;
513  zmin = ymin; zmax = ymax; z_zoomed = y_zoomed;
514  // if (!z_zoomed && (this.hmin!==this.hmax)) { zmin = this.hmin; zmax = this.hmax; }
515  ymin = 0; ymax = 1;
516  }
517 
518  // z axis range used for lego plot
519  this.lego_zmin = zmin; this.lego_zmax = zmax;
520 
521  // factor 1.1 used in ROOT for lego plots
522  if ((opts.zmult !== undefined) && !z_zoomed) zmax *= opts.zmult;
523 
524  // this.TestAxisVisibility = HPainter_TestAxisVisibility;
525 
526  if (pad && pad.fLogx) {
527  if (xmax <= 0) xmax = 1.;
528  if ((xmin <= 0) && this.xaxis)
529  for (var i=0;i<this.xaxis.fNbins;++i) {
530  xmin = Math.max(xmin, this.xaxis.GetBinLowEdge(i+1));
531  if (xmin>0) break;
532  }
533  if (xmin <= 0) xmin = 1e-4*xmax;
534  this.grx = d3.scaleLog();
535  this.x_kind = "log";
536  } else {
537  this.grx = d3.scaleLinear();
538  if (this.xaxis && this.xaxis.fLabels) this.x_kind = "labels";
539  else this.x_kind = "normal";
540  }
541 
542  this.logx = (this.x_kind === "log");
543 
544  this.grx.domain([ xmin, xmax ]).range([ grminx, grmaxx ]);
545  this.x_handle = new JSROOT.TAxisPainter(this.xaxis);
546  this.x_handle.SetAxisConfig("xaxis", this.x_kind, this.grx, this.xmin, this.xmax, xmin, xmax);
547  this.x_handle.CreateFormatFuncs();
548  this.scale_xmin = xmin; this.scale_xmax = xmax;
549 
550  if (pad && pad.fLogy && !opts.use_y_for_z) {
551  if (ymax <= 0) ymax = 1.;
552  if ((ymin <= 0) && this.yaxis)
553  for (var i=0;i<this.yaxis.fNbins;++i) {
554  ymin = Math.max(ymin, this.yaxis.GetBinLowEdge(i+1));
555  if (ymin>0) break;
556  }
557 
558  if (ymin <= 0) ymin = 1e-4*ymax;
559  this.gry = d3.scaleLog();
560  this.y_kind = "log";
561  } else {
562  this.gry = d3.scaleLinear();
563  if (this.yaxis && this.yaxis.fLabels) this.y_kind = "labels";
564  else this.y_kind = "normal";
565  }
566 
567  this.logy = (this.y_kind === "log");
568 
569  this.gry.domain([ ymin, ymax ]).range([ grminy, grmaxy ]);
570  this.y_handle = new JSROOT.TAxisPainter(this.yaxis);
571  this.y_handle.SetAxisConfig("yaxis", this.y_kind, this.gry, this.ymin, this.ymax, ymin, ymax);
572  this.y_handle.CreateFormatFuncs();
573  this.scale_ymin = ymin; this.scale_ymax = ymax;
574 
575  if (pad && pad.fLogz) {
576  if (zmax <= 0) zmax = 1;
577  if (zmin <= 0) zmin = 1e-4*zmax;
578  this.grz = d3.scaleLog();
579  this.z_kind = "log";
580  } else {
581  this.grz = d3.scaleLinear();
582  this.z_kind = "normal";
583  if (this.zaxis && this.zaxis.fLabels && (opts.ndim === 3)) this.z_kind = "labels";
584  }
585 
586  this.logz = (this.z_kind === "log");
587 
588  this.grz.domain([ zmin, zmax ]).range([ grminz, grmaxz ]);
589 
590  this.SetRootPadRange(pad, true); // set some coordinates typical for 3D projections in ROOT
591 
592  this.z_handle = new JSROOT.TAxisPainter(this.zaxis);
593  this.z_handle.SetAxisConfig("zaxis", this.z_kind, this.grz, this.zmin, this.zmax, zmin, zmax);
594  this.z_handle.CreateFormatFuncs();
595  this.scale_zmin = zmin; this.scale_zmax = zmax;
596 
597  this.x_handle.debug = true;
598 
599  var textMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 }),
600  lineMaterial = new THREE.LineBasicMaterial({ color: 0x000000 }),
601  ticklen = textsize*0.5, text, tick, lbls = [], text_scale = 1,
602  xticks = this.x_handle.CreateTicks(false, true),
603  yticks = this.y_handle.CreateTicks(false, true),
604  zticks = this.z_handle.CreateTicks(false, true);
605 
606  // main element, where all axis elements are placed
607  var top = new THREE.Object3D();
608  top.axis_draw = true; // mark element as axis drawing
609  toplevel.add(top);
610 
611  var ticks = [], maxtextheight = 0, xaxis = this.xaxis;
612 
613  while (xticks.next()) {
614  var grx = xticks.grpos,
615  is_major = (xticks.kind===1),
616  lbl = this.x_handle.format(xticks.tick, true, true);
617  if (xticks.last_major()) { if (!xaxis || !xaxis.fTitle) lbl = "x"; } else
618  if (lbl === null) { is_major = false; lbl = ""; }
619 
620  if (is_major && lbl && (lbl.length>0)) {
621  var text3d = new THREE.TextGeometry(lbl, { font: JSROOT.threejs_font_helvetiker_regular, size: textsize, height: 0, curveSegments: 5 });
622  text3d.computeBoundingBox();
623  var draw_width = text3d.boundingBox.max.x - text3d.boundingBox.min.x,
624  draw_height = text3d.boundingBox.max.y - text3d.boundingBox.min.y;
625  text3d.center = true; // place central
626 
627  // text3d.translate(-draw_width/2, 0, 0);
628 
629  maxtextheight = Math.max(maxtextheight, draw_height);
630 
631  text3d.grx = grx;
632  lbls.push(text3d);
633 
634  if (!xticks.last_major()) {
635  var space = (xticks.next_major_grpos() - grx);
636  if (draw_width > 0)
637  text_scale = Math.min(text_scale, 0.9*space/draw_width)
638  if (this.x_handle.IsCenterLabels()) text3d.grx += space/2;
639  }
640  }
641 
642  ticks.push(grx, 0, 0, grx, (is_major ? -ticklen : -ticklen * 0.6), 0);
643  }
644 
645  if (xaxis && xaxis.fTitle) {
646  var text3d = new THREE.TextGeometry(xaxis.fTitle, { font: JSROOT.threejs_font_helvetiker_regular, size: textsize, height: 0, curveSegments: 5 });
647  text3d.computeBoundingBox();
648  text3d.center = (xaxis.TestBit(JSROOT.EAxisBits.kCenterTitle));
649  text3d.gry = 2; // factor 2 shift
650  text3d.grx = (grminx + grmaxx)/2; // default position for centered title
651  lbls.push(text3d);
652  }
653 
654  this.Get3DZoomCoord = function(point, kind) {
655  // return axis coordinate from intersection point with axis geometry
656  var pos = point[kind], min = this['scale_'+kind+'min'], max = this['scale_'+kind+'max'];
657 
658  if (kind==="z") pos = pos/2/this.size_z3d;
659  else pos = (pos+this.size_xy3d)/2/this.size_xy3d;
660 
661  if (this["log"+kind]) {
662  pos = Math.exp(Math.log(min) + pos*(Math.log(max)-Math.log(min)));
663  } else {
664  pos = min + pos*(max-min);
665  }
666  return pos;
667  }
668 
669  function CreateZoomMesh(kind, size_3d, use_y_for_z) {
670  var geom = new THREE.Geometry();
671 
672  if (kind==="z")
673  geom.vertices.push(
674  new THREE.Vector3(0,0,0),
675  new THREE.Vector3(ticklen*4, 0, 0),
676  new THREE.Vector3(ticklen*4, 0, 2*size_3d),
677  new THREE.Vector3(0, 0, 2*size_3d));
678  else
679  geom.vertices.push(
680  new THREE.Vector3(-size_3d,0,0),
681  new THREE.Vector3(size_3d,0,0),
682  new THREE.Vector3(size_3d,-ticklen*4,0),
683  new THREE.Vector3(-size_3d,-ticklen*4,0));
684 
685  geom.faces.push(new THREE.Face3(0, 2, 1));
686  geom.faces.push(new THREE.Face3(0, 3, 2));
687  geom.computeFaceNormals();
688 
689  var material = new THREE.MeshBasicMaterial({ transparent: true,
690  vertexColors: THREE.NoColors, // THREE.FaceColors,
691  side: THREE.DoubleSide,
692  opacity: 0 });
693 
694  var mesh = new THREE.Mesh(geom, material);
695  mesh.zoom = kind;
696  mesh.size_3d = size_3d;
697  mesh.use_y_for_z = use_y_for_z;
698  if (kind=="y") mesh.rotateZ(Math.PI/2).rotateX(Math.PI);
699 
700  mesh.GlobalIntersect = function(raycaster) {
701  var plane = new THREE.Plane(),
702  geom = this.geometry;
703 
704  if (!geom || !geom.vertices) return undefined;
705 
706  plane.setFromCoplanarPoints(geom.vertices[0], geom.vertices[1], geom.vertices[2]);
707  plane.applyMatrix4(this.matrixWorld);
708 
709  var v1 = raycaster.ray.origin.clone(),
710  v2 = v1.clone().addScaledVector(raycaster.ray.direction, 1e10);
711 
712  var pnt = plane.intersectLine(new THREE.Line3(v1,v2), new THREE.Vector3());
713 
714  if (!pnt) return undefined;
715 
716  var min = -this.size_3d, max = this.size_3d;
717  if (this.zoom==="z") { min = 0; max = 2*this.size_3d; }
718 
719  if (pnt[this.zoom] < min) pnt[this.zoom] = min; else
720  if (pnt[this.zoom] > max) pnt[this.zoom] = max;
721 
722  return pnt;
723  }
724 
725  mesh.ShowSelection = function(pnt1,pnt2) {
726  // used to show selection
727 
728  var tgtmesh = this.children ? this.children[0] : null, gg, kind = this.zoom;
729  if (!pnt1 || !pnt2) {
730  if (tgtmesh) {
731  this.remove(tgtmesh)
732  JSROOT.Painter.DisposeThreejsObject(tgtmesh);
733  }
734  return tgtmesh;
735  }
736 
737  if (!tgtmesh) {
738  gg = this.geometry.clone();
739  if (kind==="z") gg.vertices[1].x = gg.vertices[2].x = ticklen;
740  else gg.vertices[2].y = gg.vertices[3].y = -ticklen;
741  tgtmesh = new THREE.Mesh(gg, new THREE.MeshBasicMaterial({ color: 0xFF00, side: THREE.DoubleSide }));
742  this.add(tgtmesh);
743  } else {
744  gg = tgtmesh.geometry;
745  }
746 
747  if (kind=="z") {
748  gg.vertices[0].z = gg.vertices[1].z = pnt1[kind];
749  gg.vertices[2].z = gg.vertices[3].z = pnt2[kind];
750  } else {
751  gg.vertices[0].x = gg.vertices[3].x = pnt1[kind];
752  gg.vertices[1].x = gg.vertices[2].x = pnt2[kind];
753  }
754 
755  gg.computeFaceNormals();
756 
757  gg.verticesNeedUpdate = true;
758  gg.normalsNeedUpdate = true;
759 
760  return true;
761  }
762 
763  return mesh;
764  }
765 
766  var xcont = new THREE.Object3D();
767  xcont.position.set(0, grminy, grminz)
768  xcont.rotation.x = 1/4*Math.PI;
769  xcont.xyid = 2;
770  var xtickslines = JSROOT.Painter.createLineSegments( ticks, lineMaterial );
771  xcont.add(xtickslines);
772 
773  lbls.forEach(function(lbl) {
774  var w = lbl.boundingBox.max.x - lbl.boundingBox.min.x,
775  posx = lbl.center ? lbl.grx - w/2 : grmaxx - w,
776  m = new THREE.Matrix4();
777  // matrix to swap y and z scales and shift along z to its position
778  m.set(text_scale, 0, 0, posx,
779  0, text_scale, 0, (-maxtextheight*text_scale - 1.5*ticklen) * (lbl.gry || 1),
780  0, 0, 1, 0,
781  0, 0, 0, 1);
782 
783  var mesh = new THREE.Mesh(lbl, textMaterial);
784  mesh.applyMatrix(m);
785  xcont.add(mesh);
786  });
787 
788  if (opts.zoom) xcont.add(CreateZoomMesh("x", this.size_xy3d));
789  top.add(xcont);
790 
791  xcont = new THREE.Object3D();
792  xcont.position.set(0, grmaxy, grminz);
793  xcont.rotation.x = 3/4*Math.PI;
794  xcont.add(new THREE.LineSegments(xtickslines.geometry, lineMaterial));
795  lbls.forEach(function(lbl) {
796 
797  var w = lbl.boundingBox.max.x - lbl.boundingBox.min.x,
798  posx = lbl.center ? lbl.grx + w/2 : grmaxx,
799  m = new THREE.Matrix4();
800  // matrix to swap y and z scales and shift along z to its position
801  m.set(-text_scale, 0, 0, posx,
802  0, text_scale, 0, (-maxtextheight*text_scale - 1.5*ticklen) * (lbl.gry || 1),
803  0, 0, -1, 0,
804  0, 0, 0, 1);
805  var mesh = new THREE.Mesh(lbl, textMaterial);
806  mesh.applyMatrix(m);
807  xcont.add(mesh);
808  });
809 
810  //xcont.add(new THREE.Mesh(ggg2, textMaterial));
811  xcont.xyid = 4;
812  if (opts.zoom) xcont.add(CreateZoomMesh("x", this.size_xy3d));
813  top.add(xcont);
814 
815  lbls = []; text_scale = 1; maxtextheight = 0; ticks = [];
816 
817  var yaxis = this.yaxis;
818 
819  while (yticks.next()) {
820  var gry = yticks.grpos,
821  is_major = (yticks.kind===1),
822  lbl = this.y_handle.format(yticks.tick, true, true);
823  if (yticks.last_major()) { if (!yaxis || !yaxis.fTitle) lbl = "y"; } else
824  if (lbl === null) { is_major = false; lbl = ""; }
825 
826  if (is_major) {
827  var text3d = new THREE.TextGeometry(lbl, { font: JSROOT.threejs_font_helvetiker_regular, size: textsize, height: 0, curveSegments: 5 });
828  text3d.computeBoundingBox();
829  var draw_width = text3d.boundingBox.max.x - text3d.boundingBox.min.x,
830  draw_height = text3d.boundingBox.max.y - text3d.boundingBox.min.y;
831  // text3d.translate(-draw_width/2, 0, 0);
832  text3d.center = true;
833 
834  maxtextheight = Math.max(maxtextheight, draw_height);
835 
836  text3d.gry = gry;
837  lbls.push(text3d);
838 
839  if (!yticks.last_major()) {
840  var space = (yticks.next_major_grpos() - gry);
841  if (draw_width > 0)
842  text_scale = Math.min(text_scale, 0.9*space/draw_width)
843  if (this.y_handle.IsCenterLabels()) text3d.gry += space/2;
844  }
845  }
846  ticks.push(0,gry,0, (is_major ? -ticklen : -ticklen*0.6), gry, 0);
847  }
848 
849  if (yaxis && yaxis.fTitle) {
850  var text3d = new THREE.TextGeometry(yaxis.fTitle, { font: JSROOT.threejs_font_helvetiker_regular, size: textsize, height: 0, curveSegments: 5 });
851  text3d.computeBoundingBox();
852  text3d.center = yaxis.TestBit(JSROOT.EAxisBits.kCenterTitle);
853  text3d.grx = 2; // factor 2 shift
854  text3d.gry = (grminy + grmaxy)/2; // default position for centered title
855  lbls.push(text3d);
856  }
857 
858  if (!opts.use_y_for_z) {
859  var yticksline = JSROOT.Painter.createLineSegments(ticks, lineMaterial),
860  ycont = new THREE.Object3D();
861  ycont.position.set(grminx, 0, grminz);
862  ycont.rotation.y = -1/4*Math.PI;
863  ycont.add(yticksline);
864  //ycont.add(new THREE.Mesh(ggg1, textMaterial));
865 
866  lbls.forEach(function(lbl) {
867 
868  var w = lbl.boundingBox.max.x - lbl.boundingBox.min.x,
869  posy = lbl.center ? lbl.gry + w/2 : grmaxy,
870  m = new THREE.Matrix4();
871  // matrix to swap y and z scales and shift along z to its position
872  m.set(0, text_scale, 0, (-maxtextheight*text_scale - 1.5*ticklen)*(lbl.grx || 1),
873  -text_scale, 0, 0, posy,
874  0, 0, 1, 0,
875  0, 0, 0, 1);
876 
877  var mesh = new THREE.Mesh(lbl, textMaterial);
878  mesh.applyMatrix(m);
879  ycont.add(mesh);
880  });
881 
882  ycont.xyid = 3;
883  if (opts.zoom) ycont.add(CreateZoomMesh("y", this.size_xy3d));
884  top.add(ycont);
885 
886  ycont = new THREE.Object3D();
887  ycont.position.set(grmaxx, 0, grminz);
888  ycont.rotation.y = -3/4*Math.PI;
889  ycont.add(new THREE.LineSegments(yticksline.geometry, lineMaterial));
890  //ycont.add(new THREE.Mesh(ggg2, textMaterial));
891  lbls.forEach(function(lbl) {
892  var w = lbl.boundingBox.max.x - lbl.boundingBox.min.x,
893  posy = lbl.center ? lbl.gry - w/2 : grmaxy - w,
894  m = new THREE.Matrix4();
895  m.set(0, text_scale, 0, (-maxtextheight*text_scale - 1.5*ticklen)*(lbl.grx || 1),
896  text_scale, 0, 0, posy,
897  0, 0, -1, 0,
898  0, 0, 0, 1);
899 
900  var mesh = new THREE.Mesh(lbl, textMaterial);
901  mesh.applyMatrix(m);
902  ycont.add(mesh);
903  });
904  ycont.xyid = 1;
905  if (opts.zoom) ycont.add(CreateZoomMesh("y", this.size_xy3d));
906  top.add(ycont);
907  }
908 
909 
910  lbls = []; text_scale = 1;
911 
912  ticks = []; // just array, will be used for the buffer geometry
913 
914  var zgridx = null, zgridy = null, lastmajorz = null,
915  zaxis = this.zaxis, maxzlblwidth = 0;
916 
917  if (this.size_z3d) {
918  zgridx = []; zgridy = [];
919  }
920 
921  while (zticks.next()) {
922  var grz = zticks.grpos,
923  is_major = (zticks.kind == 1),
924  lbl = this.z_handle.format(zticks.tick, true, true);
925  if (lbl === null) { is_major = false; lbl = ""; }
926 
927  if (is_major && lbl) {
928  var text3d = new THREE.TextGeometry(lbl, { font: JSROOT.threejs_font_helvetiker_regular, size: textsize, height: 0, curveSegments: 5 });
929  text3d.computeBoundingBox();
930  var draw_width = text3d.boundingBox.max.x - text3d.boundingBox.min.x,
931  draw_height = text3d.boundingBox.max.y - text3d.boundingBox.min.y;
932  text3d.translate(-draw_width, -draw_height/2, 0);
933  text3d.grz = grz;
934  lbls.push(text3d);
935 
936  if ((lastmajorz !== null) && (draw_height>0))
937  text_scale = Math.min(text_scale, 0.9*(grz - lastmajorz)/draw_height);
938 
939  maxzlblwidth = Math.max(maxzlblwidth, draw_width);
940 
941  lastmajorz = grz;
942  }
943 
944  // create grid
945  if (zgridx && is_major)
946  zgridx.push(grminx,0,grz, grmaxx,0,grz);
947 
948  if (zgridy && is_major)
949  zgridy.push(0,grminy,grz, 0,grmaxy,grz);
950 
951  ticks.push(0, 0, grz, (is_major ? ticklen : ticklen * 0.6), 0, grz);
952  }
953 
954  if (zgridx && (zgridx.length > 0)) {
955 
956  var material = new THREE.LineDashedMaterial({ color: 0x0, dashSize: 2, gapSize: 2 });
957 
958  var lines1 = JSROOT.Painter.createLineSegments(zgridx, material);
959  lines1.position.set(0,grmaxy,0);
960  lines1.grid = 2; // mark as grid
961  lines1.visible = false;
962  top.add(lines1);
963 
964  var lines2 = new THREE.LineSegments(lines1.geometry, material);
965  lines2.position.set(0,grminy,0);
966  lines2.grid = 4; // mark as grid
967  lines2.visible = false;
968  top.add(lines2);
969  }
970 
971  if (zgridy && (zgridy.length > 0)) {
972 
973  var material = new THREE.LineDashedMaterial({ color: 0x0, dashSize: 2, gapSize: 2 });
974 
975  var lines1 = JSROOT.Painter.createLineSegments(zgridy, material);
976  lines1.position.set(grmaxx,0, 0);
977  lines1.grid = 3; // mark as grid
978  lines1.visible = false;
979  top.add(lines1);
980 
981  var lines2 = new THREE.LineSegments(lines1.geometry, material);
982  lines2.position.set(grminx, 0, 0);
983  lines2.grid = 1; // mark as grid
984  lines2.visible = false;
985  top.add(lines2);
986  }
987 
988  var zcont = [], zticksline = JSROOT.Painter.createLineSegments( ticks, lineMaterial );
989  for (var n=0;n<4;++n) {
990  zcont.push(new THREE.Object3D());
991 
992  lbls.forEach(function(lbl) {
993  var m = new THREE.Matrix4();
994  // matrix to swap y and z scales and shift along z to its position
995  m.set(-text_scale, 0, 0, 2*ticklen,
996  0, 0, 1, 0,
997  0, text_scale, 0, lbl.grz);
998  var mesh = new THREE.Mesh(lbl, textMaterial);
999  mesh.applyMatrix(m);
1000  zcont[n].add(mesh);
1001  });
1002 
1003  if (zaxis && zaxis.fTitle) {
1004  var text3d = new THREE.TextGeometry(zaxis.fTitle, { font: JSROOT.threejs_font_helvetiker_regular, size: textsize, height: 0, curveSegments: 5 });
1005  text3d.computeBoundingBox();
1006  var draw_width = text3d.boundingBox.max.x - text3d.boundingBox.min.x,
1007  draw_height = text3d.boundingBox.max.y - text3d.boundingBox.min.y,
1008  posz = zaxis.TestBit(JSROOT.EAxisBits.kCenterTitle) ? (grmaxz + grminz - draw_width)/2 : grmaxz - draw_width;
1009 
1010  text3d.rotateZ(Math.PI/2);
1011 
1012  var m = new THREE.Matrix4();
1013  m.set(-text_scale, 0, 0, 3*ticklen + maxzlblwidth,
1014  0, 0, 1, 0,
1015  0, text_scale, 0, posz);
1016  var mesh = new THREE.Mesh(text3d, textMaterial);
1017  mesh.applyMatrix(m);
1018  zcont[n].add(mesh);
1019  }
1020 
1021  zcont[n].add(n==0 ? zticksline : new THREE.LineSegments(zticksline.geometry, lineMaterial));
1022  if (opts.zoom) zcont[n].add(CreateZoomMesh("z", this.size_z3d, opts.use_y_for_z));
1023 
1024  zcont[n].zid = n + 2;
1025  top.add(zcont[n]);
1026  }
1027 
1028  zcont[0].position.set(grminx,grmaxy,0);
1029  zcont[0].rotation.z = 3/4*Math.PI;
1030 
1031  zcont[1].position.set(grmaxx,grmaxy,0);
1032  zcont[1].rotation.z = 1/4*Math.PI;
1033 
1034  zcont[2].position.set(grmaxx,grminy,0);
1035  zcont[2].rotation.z = -1/4*Math.PI;
1036 
1037  zcont[3].position.set(grminx,grminy,0);
1038  zcont[3].rotation.z = -3/4*Math.PI;
1039 
1040 
1041  // for TAxis3D do not show final cube
1042  if (this.size_z3d === 0) return;
1043 
1044  var linex_geom = JSROOT.Painter.createLineSegments([grminx,0,0, grmaxx,0,0], lineMaterial, null, true);
1045  for(var n=0;n<2;++n) {
1046  var line = new THREE.LineSegments(linex_geom, lineMaterial);
1047  line.position.set(0, grminy, (n===0) ? grminz : grmaxz);
1048  line.xyboxid = 2; line.bottom = (n == 0);
1049  top.add(line);
1050 
1051  line = new THREE.LineSegments(linex_geom, lineMaterial);
1052  line.position.set(0, grmaxy, (n===0) ? grminz : grmaxz);
1053  line.xyboxid = 4; line.bottom = (n == 0);
1054  top.add(line);
1055  }
1056 
1057  var liney_geom = JSROOT.Painter.createLineSegments([0,grminy,0, 0,grmaxy,0], lineMaterial, null, true);
1058  for(var n=0;n<2;++n) {
1059  var line = new THREE.LineSegments(liney_geom, lineMaterial);
1060  line.position.set(grminx, 0, (n===0) ? grminz : grmaxz);
1061  line.xyboxid = 3; line.bottom = (n == 0);
1062  top.add(line);
1063 
1064  line = new THREE.LineSegments(liney_geom, lineMaterial);
1065  line.position.set(grmaxx, 0, (n===0) ? grminz : grmaxz);
1066  line.xyboxid = 1; line.bottom = (n == 0);
1067  top.add(line);
1068  }
1069 
1070  var linez_geom = JSROOT.Painter.createLineSegments([0,0,grminz, 0,0,grmaxz], lineMaterial, null, true);
1071  for(var n=0;n<4;++n) {
1072  var line = new THREE.LineSegments(linez_geom, lineMaterial);
1073  line.zboxid = zcont[n].zid;
1074  line.position.copy(zcont[n].position);
1075  top.add(line);
1076  }
1077  }
1078 
1080  JSROOT.THistPainter.prototype.Draw3DBins = function() {
1081 
1082  if (!this.draw_content) return;
1083 
1084  if (this.IsTH2Poly() && this.DrawPolyLego)
1085  return this.DrawPolyLego();
1086 
1087  if ((this.Dimension()==2) && this.options.Contour && this.DrawContour3D)
1088  return this.DrawContour3D(true);
1089 
1090  if ((this.Dimension()==2) && this.options.Surf && this.DrawSurf)
1091  return this.DrawSurf();
1092 
1093  if ((this.Dimension()==2) && this.options.Error && this.DrawError)
1094  return this.DrawError();
1095 
1096  // Perform TH1/TH2 lego plot with BufferGeometry
1097 
1098  var vertices = JSROOT.Painter.Box3D.Vertices,
1099  indicies = JSROOT.Painter.Box3D.Indexes,
1100  vnormals = JSROOT.Painter.Box3D.Normals,
1101  segments = JSROOT.Painter.Box3D.Segments,
1102  // reduced line segments
1103  rsegments = [0, 1, 1, 2, 2, 3, 3, 0],
1104  // reduced vertices
1105  rvertices = [ new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 1, 0), new THREE.Vector3(1, 1, 0), new THREE.Vector3(1, 0, 0) ],
1106  main = this.frame_painter(),
1107  axis_zmin = main.grz.domain()[0],
1108  axis_zmax = main.grz.domain()[1],
1109  handle = this.PrepareColorDraw({ rounding: false, use3d: true, extra: 1 }),
1110  i1 = handle.i1, i2 = handle.i2, j1 = handle.j1, j2 = handle.j2,
1111  i, j, x1, x2, y1, y2, binz1, binz2, reduced, nobottom, notop,
1112  pthis = this,
1113  histo = this.GetHisto(),
1114  basehisto = histo ? histo.$baseh : null,
1115  split_faces = (this.options.Lego === 11) || (this.options.Lego === 13); // split each layer on two parts
1116 
1117  if ((i1 >= i2) || (j1 >= j2)) return;
1118 
1119  function GetBinContent(ii,jj, level) {
1120  // return bin content in binz1, binz2, reduced flags
1121  // return true if bin should be displayed
1122 
1123  binz2 = histo.getBinContent(ii+1, jj+1);
1124  if (basehisto)
1125  binz1 = basehisto.getBinContent(ii+1, jj+1);
1126  else if (pthis.options.BaseLine !== false)
1127  binz1 = pthis.options.BaseLine;
1128  else
1129  binz1 = pthis.options.Zero ? axis_zmin : 0;
1130  if (binz2 < binz1) { var d = binz1; binz1 = binz2; binz2 = d; }
1131 
1132  if ((binz1 >= zmax) || (binz2 < zmin)) return false;
1133 
1134  reduced = (binz2 === zmin) || (binz1 >= binz2);
1135 
1136  if (!reduced || (level>0)) return true;
1137 
1138  if (histo['$baseh']) return false; // do not draw empty bins on top of other bins
1139 
1140  if (pthis.options.Zero || (axis_zmin>0)) return true;
1141 
1142  return pthis._show_empty_bins;
1143  }
1144 
1145  // if bin ID fit into 16 bit, use smaller arrays for intersect indexes
1146  var use16indx = (histo.getBin(i2, j2) < 0xFFFF),
1147  levels = [ axis_zmin, axis_zmax ], palette = null, totalvertices = 0;
1148 
1149  // DRAW ALL CUBES
1150 
1151  if ((this.options.Lego === 12) || (this.options.Lego === 14)) {
1152  // drawing colors levels, axis can not exceed palette
1153  levels = this.CreateContour(histo.fContour ? histo.fContour.length : 20, main.lego_zmin, main.lego_zmax);
1154  palette = this.GetPalette();
1155  //axis_zmin = levels[0];
1156  //axis_zmax = levels[levels.length-1];
1157  }
1158 
1159  for (var nlevel=0; nlevel<levels.length-1;++nlevel) {
1160 
1161  var zmin = levels[nlevel], zmax = levels[nlevel+1],
1162  z1 = 0, z2 = 0, numvertices = 0, num2vertices = 0;
1163 
1164  // artifically extend last level of color pallette to maximial visible value
1165  if (palette && (nlevel==levels.length-2) && zmax < axis_zmax) zmax = axis_zmax;
1166 
1167  var grzmin = main.grz(zmin), grzmax = main.grz(zmax);
1168 
1169  // now calculate size of buffer geometry for boxes
1170 
1171  for (i=i1;i<i2;++i)
1172  for (j=j1;j<j2;++j) {
1173 
1174  if (!GetBinContent(i,j,nlevel)) continue;
1175 
1176  nobottom = !reduced && (nlevel>0);
1177  notop = !reduced && (binz2 > zmax) && (nlevel < levels.length-2);
1178 
1179  numvertices += (reduced ? 12 : indicies.length);
1180  if (nobottom) numvertices -= 6;
1181  if (notop) numvertices -= 6;
1182 
1183  if (split_faces && !reduced) {
1184  numvertices -= 12;
1185  num2vertices += 12;
1186  }
1187  }
1188 
1189  totalvertices += numvertices + num2vertices;
1190 
1191  var positions = new Float32Array(numvertices*3),
1192  normals = new Float32Array(numvertices*3),
1193  face_to_bins_index = use16indx ? new Uint16Array(numvertices/3) : new Uint32Array(numvertices/3),
1194  pos2 = null, norm2 = null, face_to_bins_indx2 = null,
1195  v = 0, v2 = 0, vert, bin, k, nn;
1196 
1197  if (num2vertices > 0) {
1198  pos2 = new Float32Array(num2vertices*3);
1199  norm2 = new Float32Array(num2vertices*3);
1200  face_to_bins_indx2 = use16indx ? new Uint16Array(num2vertices/3) : new Uint32Array(num2vertices/3);
1201  }
1202 
1203  for (i=i1;i<i2;++i) {
1204  x1 = handle.grx[i] + handle.xbar1*(handle.grx[i+1]-handle.grx[i]);
1205  x2 = handle.grx[i] + handle.xbar2*(handle.grx[i+1]-handle.grx[i]);
1206  for (j=j1;j<j2;++j) {
1207 
1208  if (!GetBinContent(i,j,nlevel)) continue;
1209 
1210  nobottom = !reduced && (nlevel>0);
1211  notop = !reduced && (binz2 > zmax) && (nlevel < levels.length-2);
1212 
1213  y1 = handle.gry[j] + handle.ybar1*(handle.gry[j+1] - handle.gry[j]);
1214  y2 = handle.gry[j] + handle.ybar2*(handle.gry[j+1] - handle.gry[j]);
1215 
1216  z1 = (binz1 <= zmin) ? grzmin : main.grz(binz1);
1217  z2 = (binz2 > zmax) ? grzmax : main.grz(binz2);
1218 
1219  nn = 0; // counter over the normals, each normals correspond to 6 vertices
1220  k = 0; // counter over vertices
1221 
1222  if (reduced) {
1223  // we skip all side faces, keep only top and bottom
1224  nn += 12;
1225  k += 24;
1226  }
1227 
1228  var size = indicies.length, bin_index = histo.getBin(i+1, j+1);
1229  if (nobottom) size -= 6;
1230 
1231  // array over all vertices of the single bin
1232  while(k < size) {
1233 
1234  vert = vertices[indicies[k]];
1235 
1236  if (split_faces && (k < 12)) {
1237  pos2[v2] = x1 + vert.x * (x2 - x1);
1238  pos2[v2+1] = y1 + vert.y * (y2 - y1);
1239  pos2[v2+2] = z1 + vert.z * (z2 - z1);
1240 
1241  norm2[v2] = vnormals[nn];
1242  norm2[v2+1] = vnormals[nn+1];
1243  norm2[v2+2] = vnormals[nn+2];
1244  if (v2%9===0) face_to_bins_indx2[v2/9] = bin_index; // remember which bin corresponds to the face
1245  v2+=3;
1246  } else {
1247  positions[v] = x1 + vert.x * (x2 - x1);
1248  positions[v+1] = y1 + vert.y * (y2 - y1);
1249  positions[v+2] = z1 + vert.z * (z2 - z1);
1250 
1251  normals[v] = vnormals[nn];
1252  normals[v+1] = vnormals[nn+1];
1253  normals[v+2] = vnormals[nn+2];
1254  if (v%9===0) face_to_bins_index[v/9] = bin_index; // remember which bin corresponds to the face
1255  v+=3;
1256  }
1257 
1258  ++k;
1259 
1260  if (k%6 === 0) {
1261  nn+=3;
1262  if (notop && (k === indicies.length - 12)) {
1263  k+=6; nn+=3; // jump over notop indexes
1264  }
1265  }
1266  }
1267  }
1268  }
1269 
1270  var geometry = new THREE.BufferGeometry();
1271  geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
1272  geometry.addAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) );
1273  // geometry.computeVertexNormals();
1274 
1275  var rootcolor = histo.fFillColor,
1276  fcolor = this.get_color(rootcolor);
1277 
1278  if (palette) {
1279  fcolor = palette.calcColor(nlevel, levels.length);
1280  } else {
1281  if ((this.options.Lego === 1) || (rootcolor < 2)) {
1282  rootcolor = 1;
1283  fcolor = 'white';
1284  }
1285  }
1286 
1287  //var material = new THREE.MeshLambertMaterial( { color: fcolor } );
1288  var material = new THREE.MeshBasicMaterial( { color: fcolor, flatShading: true } );
1289 
1290  var mesh = new THREE.Mesh(geometry, material);
1291 
1292  mesh.face_to_bins_index = face_to_bins_index;
1293  mesh.painter = this;
1294  mesh.zmin = axis_zmin;
1295  mesh.zmax = axis_zmax;
1296  mesh.baseline = (this.options.BaseLine!==false) ? this.options.BaseLine : (this.options.Zero ? axis_zmin : 0);
1297  mesh.tip_color = (rootcolor===3) ? 0xFF0000 : 0x00FF00;
1298  mesh.handle = handle;
1299 
1300  mesh.tooltip = function(intersect) {
1301  if (isNaN(intersect.faceIndex)) {
1302  console.error('faceIndex not provided, check three.js version', THREE.REVISION, 'expected r97');
1303  return null;
1304  }
1305 
1306  if ((intersect.faceIndex < 0) || (intersect.faceIndex >= this.face_to_bins_index.length)) return null;
1307 
1308  var p = this.painter,
1309  handle = this.handle,
1310  main = p.frame_painter(),
1311  histo = p.GetHisto(),
1312  tip = p.Get3DToolTip( this.face_to_bins_index[intersect.faceIndex] );
1313 
1314  tip.x1 = Math.max(-main.size_xy3d, handle.grx[tip.ix-1] + handle.xbar1*(handle.grx[tip.ix]-handle.grx[tip.ix-1]));
1315  tip.x2 = Math.min(main.size_xy3d, handle.grx[tip.ix-1] + handle.xbar2*(handle.grx[tip.ix]-handle.grx[tip.ix-1]));
1316 
1317  tip.y1 = Math.max(-main.size_xy3d, handle.gry[tip.iy-1] + handle.ybar1*(handle.gry[tip.iy] - handle.gry[tip.iy-1]));
1318  tip.y2 = Math.min(main.size_xy3d, handle.gry[tip.iy-1] + handle.ybar2*(handle.gry[tip.iy] - handle.gry[tip.iy-1]));
1319 
1320  var binz1 = this.baseline, binz2 = tip.value;
1321  if (histo['$baseh']) binz1 = histo['$baseh'].getBinContent(tip.ix, tip.iy);
1322  if (binz2<binz1) { var v = binz1; binz1 = binz2; binz2 = v; }
1323 
1324  tip.z1 = main.grz(Math.max(this.zmin,binz1));
1325  tip.z2 = main.grz(Math.min(this.zmax,binz2));
1326 
1327  tip.color = this.tip_color;
1328 
1329  if (p.is_projection && (p.Dimension()==2)) tip.$painter = p; // used only for projections
1330 
1331  return tip;
1332  }
1333 
1334  main.toplevel.add(mesh);
1335 
1336  if (num2vertices > 0) {
1337  var geom2 = new THREE.BufferGeometry();
1338  geom2.addAttribute( 'position', new THREE.BufferAttribute( pos2, 3 ) );
1339  geom2.addAttribute( 'normal', new THREE.BufferAttribute( norm2, 3 ) );
1340  //geom2.computeVertexNormals();
1341 
1342  //var material2 = new THREE.MeshLambertMaterial( { color: 0xFF0000 } );
1343 
1344  var color2 = (rootcolor<2) ? new THREE.Color(0xFF0000) :
1345  new THREE.Color(d3.rgb(fcolor).darker(0.5).toString());
1346 
1347  var material2 = new THREE.MeshBasicMaterial( { color: color2, flatShading: true } );
1348 
1349  var mesh2 = new THREE.Mesh(geom2, material2);
1350  mesh2.face_to_bins_index = face_to_bins_indx2;
1351  mesh2.painter = this;
1352  mesh2.handle = mesh.handle;
1353  mesh2.tooltip = mesh.tooltip;
1354  mesh2.zmin = mesh.zmin;
1355  mesh2.zmax = mesh.zmax;
1356  mesh2.baseline = mesh.baseline;
1357  mesh2.tip_color = mesh.tip_color;
1358 
1359  main.toplevel.add(mesh2);
1360  }
1361  }
1362 
1363  // lego3 or lego4 do not draw border lines
1364  if (this.options.Lego > 12) return;
1365 
1366  // DRAW LINE BOXES
1367 
1368  var numlinevertices = 0, numsegments = 0, uselineindx = true, nskip = 0;
1369 
1370  zmax = axis_zmax; zmin = axis_zmin;
1371 
1372  for (i=i1;i<i2;++i)
1373  for (j=j1;j<j2;++j) {
1374  if (!GetBinContent(i,j,0)) { nskip++; continue; }
1375 
1376  // calculate required buffer size for line segments
1377  numlinevertices += (reduced ? rvertices.length : vertices.length);
1378  numsegments += (reduced ? rsegments.length : segments.length);
1379  }
1380 
1381  // On some platforms vertex index required to be Uint16 array
1382  // While we cannot use index for large vertex list
1383  // skip index usage at all. It happens for relatively large histograms (100x100 bins)
1384  if (numlinevertices > 0xFFF0) uselineindx = false;
1385 
1386  if (!uselineindx) numlinevertices = numsegments*3;
1387 
1388  var lpositions = new Float32Array( numlinevertices * 3 ),
1389  lindicies = uselineindx ? new Uint16Array( numsegments ) : null,
1390  grzmin = main.grz(axis_zmin),
1391  grzmax = main.grz(axis_zmax),
1392  z1 = 0, z2 = 0, ll = 0, ii = 0;
1393 
1394  for (i=i1;i<i2;++i) {
1395  x1 = handle.grx[i] + handle.xbar1*(handle.grx[i+1]-handle.grx[i]);
1396  x2 = handle.grx[i] + handle.xbar2*(handle.grx[i+1]-handle.grx[i]);
1397  for (j=j1;j<j2;++j) {
1398 
1399  if (!GetBinContent(i,j,0)) continue;
1400 
1401  y1 = handle.gry[j] + handle.ybar1*(handle.gry[j+1] - handle.gry[j]);
1402  y2 = handle.gry[j] + handle.ybar2*(handle.gry[j+1] - handle.gry[j]);
1403 
1404  z1 = (binz1 <= axis_zmin) ? grzmin : main.grz(binz1);
1405  z2 = (binz2 > axis_zmax) ? grzmax : main.grz(binz2);
1406 
1407  var seg = reduced ? rsegments : segments,
1408  vvv = reduced ? rvertices : vertices;
1409 
1410  if (uselineindx) {
1411  // array of indicies for the lines, to avoid duplication of points
1412  for (k=0; k < seg.length; ++k) {
1413 // intersect_index[ii] = bin_index;
1414  lindicies[ii++] = ll/3 + seg[k];
1415  }
1416 
1417  for (k=0; k < vvv.length; ++k) {
1418  vert = vvv[k];
1419  lpositions[ll] = x1 + vert.x * (x2 - x1);
1420  lpositions[ll+1] = y1 + vert.y * (y2 - y1);
1421  lpositions[ll+2] = z1 + vert.z * (z2 - z1);
1422  ll+=3;
1423  }
1424  } else {
1425  // copy only vertex positions
1426  for (k=0; k < seg.length; ++k) {
1427  vert = vvv[seg[k]];
1428  lpositions[ll] = x1 + vert.x * (x2 - x1);
1429  lpositions[ll+1] = y1 + vert.y * (y2 - y1);
1430  lpositions[ll+2] = z1 + vert.z * (z2 - z1);
1431 // intersect_index[ll/3] = bin_index;
1432  ll+=3;
1433  }
1434  }
1435  }
1436  }
1437 
1438  // create boxes
1439  var lcolor = this.get_color(histo.fLineColor);
1440  material = new THREE.LineBasicMaterial({ color: new THREE.Color(lcolor) });
1441  if (!JSROOT.browser.isIE) material.linewidth = histo.fLineWidth;
1442 
1443  var line = JSROOT.Painter.createLineSegments(lpositions, material, uselineindx ? lindicies : null );
1444 
1445  /*
1446  line.painter = this;
1447  line.intersect_index = intersect_index;
1448  line.tooltip = function(intersect) {
1449  if ((intersect.index<0) || (intersect.index >= this.intersect_index.length)) return null;
1450  return this.painter.Get3DToolTip(this.intersect_index[intersect.index]);
1451  }
1452  */
1453 
1454  main.toplevel.add(line);
1455  }
1456 
1457  // ===================================================================================
1458 
1459  JSROOT.Painter.drawAxis3D = function(divid, axis, opt) {
1460 
1461  var painter = new JSROOT.TObjectPainter(axis);
1462 
1463  if (!('_main' in axis))
1464  painter.SetDivId(divid);
1465 
1466  painter.Draw3DAxis = function() {
1467  var main = this.frame_painter();
1468 
1469  if (!main || !main._toplevel)
1470  return console.warn('no geo object found for 3D axis drawing');
1471 
1472  var box = new THREE.Box3().setFromObject(main._toplevel);
1473 
1474  this.xmin = box.min.x; this.xmax = box.max.x;
1475  this.ymin = box.min.y; this.ymax = box.max.y;
1476  this.zmin = box.min.z; this.zmax = box.max.z;
1477 
1478  // use min/max values directly as graphical coordinates
1479  this.size_xy3d = this.size_z3d = 0;
1480 
1481  this.DrawXYZ = JSROOT.TFramePainter.prototype.DrawXYZ; // just reuse axis drawing from frame painter
1482 
1483  this.DrawXYZ(main._toplevel);
1484 
1485  main.adjustCameraPosition();
1486 
1487  main.Render3D();
1488  }
1489 
1490  painter.Draw3DAxis();
1491 
1492  return painter.DrawingReady();
1493  }
1494 
1495  // ==========================================================================================
1496 
1498  JSROOT.TH1Painter.prototype.Draw3D = function(call_back, resize) {
1499 
1500  this.mode3d = true;
1501 
1502  var main = this.frame_painter(), // who makes axis drawing
1503  is_main = this.is_main_painter(), // is main histogram
1504  histo = this.GetHisto();
1505 
1506  if (resize) {
1507 
1508  if (is_main && main.Resize3D()) main.Render3D();
1509 
1510  } else {
1511 
1512  this.DeleteAtt();
1513 
1514  this.ScanContent(true); // may be required for axis drawings
1515 
1516  if (is_main) {
1517  main.Create3DScene();
1518  main.SetAxesRanges(histo.fXaxis, this.xmin, this.xmax, histo.fYaxis, this.ymin, this.ymax, histo.fZaxis, 0, 0);
1519  main.Set3DOptions(this.options);
1520  main.DrawXYZ(main.toplevel, { use_y_for_z: true, zmult: 1.1, zoom: JSROOT.gStyle.Zooming, ndim: 1 });
1521  }
1522 
1523  if (main.mode3d) {
1524  this.Draw3DBins();
1525  main.Render3D();
1526  this.UpdateStatWebCanvas();
1527  main.AddKeysHandler();
1528  }
1529  }
1530 
1531  if (is_main) {
1532  // (re)draw palette by resize while canvas may change dimension
1533  this.DrawColorPalette(this.options.Zscale && ((this.options.Lego===12) || (this.options.Lego===14)));
1534 
1535  this.DrawTitle();
1536  }
1537 
1538  JSROOT.CallBack(call_back);
1539  }
1540 
1541  // ==========================================================================================
1542 
1543  JSROOT.TH2Painter.prototype.Draw3D = function(call_back, resize) {
1544 
1545  this.mode3d = true;
1546 
1547  var main = this.frame_painter(), // who makes axis drawing
1548  is_main = this.is_main_painter(), // is main histogram
1549  histo = this.GetHisto();
1550 
1551  if (resize) {
1552 
1553  if (is_main && main.Resize3D()) main.Render3D();
1554 
1555  } else {
1556 
1557  var pad = this.root_pad(), zmult = 1.1;
1558 
1559  this.zmin = pad.fLogz ? this.gminposbin * 0.3 : this.gminbin;
1560  this.zmax = this.gmaxbin;
1561 
1562  if (this.options.minimum !== -1111) this.zmin = this.options.minimum;
1563  if (this.options.maximum !== -1111) { this.zmax = this.options.maximum; zmult = 1; }
1564 
1565  if (pad.fLogz && (this.zmin<=0)) this.zmin = this.zmax * 1e-5;
1566 
1567  this.DeleteAtt();
1568 
1569  if (is_main) {
1570  main.Create3DScene();
1571  main.SetAxesRanges(histo.fXaxis, this.xmin, this.xmax, histo.fYaxis, this.ymin, this.ymax, histo.fZaxis, this.zmin, this.zmax);
1572  main.Set3DOptions(this.options);
1573  main.DrawXYZ(main.toplevel, { zmult: zmult, zoom: JSROOT.gStyle.Zooming, ndim: 2 });
1574  }
1575 
1576  if (main.mode3d) {
1577  this.Draw3DBins();
1578  main.Render3D();
1579  this.UpdateStatWebCanvas();
1580  main.AddKeysHandler();
1581  }
1582  }
1583 
1584  if (is_main) {
1585 
1586  // (re)draw palette by resize while canvas may change dimension
1587  this.DrawColorPalette(this.options.Zscale && ((this.options.Lego===12) || (this.options.Lego===14) ||
1588  (this.options.Surf===11) || (this.options.Surf===12)));
1589 
1590  this.DrawTitle();
1591  }
1592 
1593  JSROOT.CallBack(call_back);
1594  }
1595 
1596  JSROOT.TH2Painter.prototype.DrawContour3D = function(realz) {
1597  // for contour plots one requires handle with full range
1598  var main = this.frame_painter(),
1599  handle = this.PrepareColorDraw({rounding: false, use3d: true, extra: 100, middle: 0.0 }),
1600  histo = this.GetHisto(), // get levels
1601  levels = this.GetContour(), // init contour if not exists
1602  palette = this.GetPalette(),
1603  layerz = 2*main.size_z3d, pnts = [];
1604 
1605  this.BuildContour(handle, levels, palette,
1606  function(colindx,xp,yp,iminus,iplus,ilevel) {
1607  // ignore less than three points
1608  if (iplus - iminus < 3) return;
1609 
1610  if (realz) {
1611  layerz = main.grz(levels[ilevel]);
1612  if ((layerz < 0) || (layerz > 2*main.size_z3d)) return;
1613  }
1614 
1615  for (var i=iminus;i<iplus;++i) {
1616  pnts.push(xp[i], yp[i], layerz);
1617  pnts.push(xp[i+1], yp[i+1], layerz);
1618  }
1619  }
1620  );
1621 
1622  var lines = JSROOT.Painter.createLineSegments(pnts, JSROOT.Painter.Create3DLineMaterial(this, histo));
1623  main.toplevel.add(lines);
1624  }
1625 
1626  JSROOT.TH2Painter.prototype.DrawSurf = function() {
1627  var histo = this.GetHisto(),
1628  main = this.frame_painter(),
1629  handle = this.PrepareColorDraw({rounding: false, use3d: true, extra: 1, middle: 0.5 }),
1630  i,j, x1, y1, x2, y2, z11, z12, z21, z22,
1631  axis_zmin = main.grz.domain()[0],
1632  axis_zmax = main.grz.domain()[1];
1633 
1634  // first adjust ranges
1635 
1636  var main_grz = !main.logz ? main.grz : function(value) { return value < axis_zmin ? -0.1 : main.grz(value); }
1637 
1638  if ((handle.i2 - handle.i1 < 2) || (handle.j2 - handle.j1 < 2)) return;
1639 
1640  var ilevels = null, levels = null, dolines = true, dogrid = false,
1641  donormals = false, palette = null;
1642 
1643  switch(this.options.Surf) {
1644  case 11: ilevels = this.GetContour(); palette = this.GetPalette(); break;
1645  case 12:
1646  case 15: // make surf5 same as surf2
1647  case 17: ilevels = this.GetContour(); palette = this.GetPalette(); dolines = false; break;
1648  case 14: dolines = false; donormals = true; break;
1649  case 16: ilevels = this.GetContour(); dogrid = true; dolines = false; break;
1650  default: ilevels = main.z_handle.CreateTicks(true); dogrid = true; break;
1651  }
1652 
1653  if (ilevels) {
1654  // recalculate levels into graphical coordinates
1655  levels = new Float32Array(ilevels.length);
1656  for (var ll=0;ll<ilevels.length;++ll)
1657  levels[ll] = main_grz(ilevels[ll]);
1658  } else {
1659  levels = [0, 2*main.size_z3d]; // just cut top/bottom parts
1660  }
1661 
1662  var loop, nfaces = [], pos = [], indx = [], // buffers for faces
1663  nsegments = 0, lpos = null, lindx = 0, // buffer for lines
1664  ngridsegments = 0, grid = null, gindx = 0, // buffer for grid lines segments
1665  normindx = null; // buffer to remember place of vertex for each bin
1666 
1667  function CheckSide(z,level1, level2) {
1668  if (z<level1) return -1;
1669  if (z>level2) return 1;
1670  return 0;
1671  }
1672 
1673  function AddLineSegment(x1,y1,z1, x2,y2,z2) {
1674  if (!dolines) return;
1675  var side1 = CheckSide(z1,0,2*main.size_z3d),
1676  side2 = CheckSide(z2,0,2*main.size_z3d);
1677  if ((side1===side2) && (side1!==0)) return;
1678  if (!loop) return ++nsegments;
1679 
1680  if (side1!==0) {
1681  var diff = z2-z1;
1682  z1 = (side1<0) ? 0 : 2*main.size_z3d;
1683  x1 = x2 - (x2-x1)/diff*(z2-z1);
1684  y1 = y2 - (y2-y1)/diff*(z2-z1);
1685  }
1686  if (side2!==0) {
1687  var diff = z1-z2;
1688  z2 = (side2<0) ? 0 : 2*main.size_z3d;
1689  x2 = x1 - (x1-x2)/diff*(z1-z2);
1690  y2 = y1 - (y1-y2)/diff*(z1-z2);
1691  }
1692 
1693  lpos[lindx] = x1; lpos[lindx+1] = y1; lpos[lindx+2] = z1; lindx+=3;
1694  lpos[lindx] = x2; lpos[lindx+1] = y2; lpos[lindx+2] = z2; lindx+=3;
1695  }
1696 
1697  var pntbuf = new Float32Array(6*3), k = 0, lastpart = 0; // maximal 6 points
1698  var gridpnts = new Float32Array(2*3), gridcnt = 0;
1699 
1700  function AddCrossingPoint(xx1,yy1,zz1, xx2,yy2,zz2, crossz, with_grid) {
1701  if (k>=pntbuf.length) console.log('more than 6 points???');
1702 
1703  var part = (crossz - zz1) / (zz2 - zz1), shift = 3;
1704  if ((lastpart!==0) && (Math.abs(part) < Math.abs(lastpart))) {
1705  // while second crossing point closer than first to original, move it in memory
1706  pntbuf[k] = pntbuf[k-3];
1707  pntbuf[k+1] = pntbuf[k-2];
1708  pntbuf[k+2] = pntbuf[k-1];
1709  k-=3; shift = 6;
1710  }
1711 
1712  pntbuf[k] = xx1 + part*(xx2-xx1);
1713  pntbuf[k+1] = yy1 + part*(yy2-yy1);
1714  pntbuf[k+2] = crossz;
1715 
1716  if (with_grid && grid) {
1717  gridpnts[gridcnt] = pntbuf[k];
1718  gridpnts[gridcnt+1] = pntbuf[k+1];
1719  gridpnts[gridcnt+2] = pntbuf[k+2];
1720  gridcnt+=3;
1721  }
1722 
1723  k += shift;
1724  lastpart = part;
1725  }
1726 
1727  function RememberVertex(indx, ii,jj) {
1728  var bin = ((ii-handle.i1) * (handle.j2-handle.j1) + (jj-handle.j1))*8;
1729 
1730  if (normindx[bin]>=0)
1731  return console.error('More than 8 vertexes for the bin');
1732 
1733  var pos = bin+8+normindx[bin]; // position where write index
1734  normindx[bin]--;
1735  normindx[pos] = indx; // at this moment index can be overwritten, means all 8 position are there
1736  }
1737 
1738  function RecalculateNormals(arr) {
1739  for (var ii=handle.i1;ii<handle.i2;++ii) {
1740  for (var jj=handle.j1;jj<handle.j2;++jj) {
1741  var bin = ((ii-handle.i1) * (handle.j2-handle.j1) + (jj-handle.j1)) * 8;
1742 
1743  if (normindx[bin] === -1) continue; // nothing there
1744 
1745  var beg = (normindx[bin] >=0) ? bin : bin+9+normindx[bin],
1746  end = bin+8, sumx=0, sumy = 0, sumz = 0;
1747 
1748  for (var kk=beg;kk<end;++kk) {
1749  var indx = normindx[kk];
1750  if (indx<0) return console.error('FAILURE in NORMALS RECALCULATIONS');
1751  sumx+=arr[indx];
1752  sumy+=arr[indx+1];
1753  sumz+=arr[indx+2];
1754  }
1755 
1756  sumx = sumx/(end-beg); sumy = sumy/(end-beg); sumz = sumz/(end-beg);
1757 
1758  for (var kk=beg;kk<end;++kk) {
1759  var indx = normindx[kk];
1760  arr[indx] = sumx;
1761  arr[indx+1] = sumy;
1762  arr[indx+2] = sumz;
1763  }
1764  }
1765  }
1766  }
1767 
1768  function AddMainTriangle(x1,y1,z1, x2,y2,z2, x3,y3,z3, is_first) {
1769 
1770  for (var lvl=1;lvl<levels.length;++lvl) {
1771 
1772  var side1 = CheckSide(z1, levels[lvl-1], levels[lvl]),
1773  side2 = CheckSide(z2, levels[lvl-1], levels[lvl]),
1774  side3 = CheckSide(z3, levels[lvl-1], levels[lvl]),
1775  side_sum = side1 + side2 + side3;
1776 
1777  if (side_sum === 3) continue;
1778  if (side_sum === -3) return;
1779 
1780  if (!loop) {
1781  var npnts = Math.abs(side2-side1) + Math.abs(side3-side2) + Math.abs(side1-side3);
1782  if (side1===0) ++npnts;
1783  if (side2===0) ++npnts;
1784  if (side3===0) ++npnts;
1785 
1786  if ((npnts===1) || (npnts===2)) console.error('FOND npnts', npnts);
1787 
1788  if (npnts>2) {
1789  if (nfaces[lvl]===undefined) nfaces[lvl] = 0;
1790  nfaces[lvl] += npnts-2;
1791  }
1792 
1793  // check if any(contours for given level exists
1794  if (((side1>0) || (side2>0) || (side3>0)) &&
1795  ((side1!==side2) || (side2!==side3) || (side3!==side1))) ++ngridsegments;
1796 
1797  continue;
1798  }
1799 
1800  gridcnt = 0;
1801 
1802  k = 0;
1803  if (side1 === 0) { pntbuf[k] = x1; pntbuf[k+1] = y1; pntbuf[k+2] = z1; k+=3; }
1804 
1805  if (side1!==side2) {
1806  // order is important, should move from 1->2 point, checked via lastpart
1807  lastpart = 0;
1808  if ((side1<0) || (side2<0)) AddCrossingPoint(x1,y1,z1, x2,y2,z2, levels[lvl-1]);
1809  if ((side1>0) || (side2>0)) AddCrossingPoint(x1,y1,z1, x2,y2,z2, levels[lvl], true);
1810  }
1811 
1812  if (side2 === 0) { pntbuf[k] = x2; pntbuf[k+1] = y2; pntbuf[k+2] = z2; k+=3; }
1813 
1814  if (side2!==side3) {
1815  // order is important, should move from 2->3 point, checked via lastpart
1816  lastpart = 0;
1817  if ((side2<0) || (side3<0)) AddCrossingPoint(x2,y2,z2, x3,y3,z3, levels[lvl-1]);
1818  if ((side2>0) || (side3>0)) AddCrossingPoint(x2,y2,z2, x3,y3,z3, levels[lvl], true);
1819  }
1820 
1821  if (side3 === 0) { pntbuf[k] = x3; pntbuf[k+1] = y3; pntbuf[k+2] = z3; k+=3; }
1822 
1823  if (side3!==side1) {
1824  // order is important, should move from 3->1 point, checked via lastpart
1825  lastpart = 0;
1826  if ((side3<0) || (side1<0)) AddCrossingPoint(x3,y3,z3, x1,y1,z1, levels[lvl-1]);
1827  if ((side3>0) || (side1>0)) AddCrossingPoint(x3,y3,z3, x1,y1,z1, levels[lvl], true);
1828  }
1829 
1830  if (k===0) continue;
1831  if (k<9) { console.log('found less than 3 points', k/3); continue; }
1832 
1833  if (grid && (gridcnt === 6)) {
1834  for (var jj=0;jj < 6; ++jj)
1835  grid[gindx+jj] = gridpnts[jj];
1836  gindx+=6;
1837  }
1838 
1839 
1840  // if three points and surf==14, remember vertex for each point
1841 
1842  var buf = pos[lvl], s = indx[lvl];
1843  if (donormals && (k===9)) {
1844  RememberVertex(s, i, j);
1845  RememberVertex(s+3, i+1, is_first ? j+1 : j);
1846  RememberVertex(s+6, is_first ? i : i+1, j+1);
1847  }
1848 
1849  for (var k1=3;k1<k-3;k1+=3) {
1850  buf[s] = pntbuf[0]; buf[s+1] = pntbuf[1]; buf[s+2] = pntbuf[2]; s+=3;
1851  buf[s] = pntbuf[k1]; buf[s+1] = pntbuf[k1+1]; buf[s+2] = pntbuf[k1+2]; s+=3;
1852  buf[s] = pntbuf[k1+3]; buf[s+1] = pntbuf[k1+4]; buf[s+2] = pntbuf[k1+5]; s+=3;
1853  }
1854  indx[lvl] = s;
1855 
1856  }
1857  }
1858 
1859  if (donormals) {
1860  // for each bin maximal 8 points reserved
1861  normindx = new Int32Array((handle.i2-handle.i1)*(handle.j2-handle.j1)*8);
1862  for (var n=0;n<normindx.length;++n) normindx[n] = -1;
1863  }
1864 
1865  for (loop=0;loop<2;++loop) {
1866  if (loop) {
1867  for (var lvl=1;lvl<levels.length;++lvl)
1868  if (nfaces[lvl]) {
1869  pos[lvl] = new Float32Array(nfaces[lvl] * 9);
1870  indx[lvl] = 0;
1871  }
1872  if (dolines && (nsegments > 0))
1873  lpos = new Float32Array(nsegments * 6);
1874  if (dogrid && (ngridsegments>0))
1875  grid = new Float32Array(ngridsegments * 6);
1876  }
1877  for (i=handle.i1;i<handle.i2-1;++i) {
1878  x1 = handle.grx[i];
1879  x2 = handle.grx[i+1];
1880  for (j=handle.j1;j<handle.j2-1;++j) {
1881  y1 = handle.gry[j];
1882  y2 = handle.gry[j+1];
1883  z11 = main_grz(histo.getBinContent(i+1, j+1));
1884  z12 = main_grz(histo.getBinContent(i+1, j+2));
1885  z21 = main_grz(histo.getBinContent(i+2, j+1));
1886  z22 = main_grz(histo.getBinContent(i+2, j+2));
1887 
1888  AddMainTriangle(x1,y1,z11, x2,y2,z22, x1,y2,z12, true);
1889 
1890  AddMainTriangle(x1,y1,z11, x2,y1,z21, x2,y2,z22, false);
1891 
1892  AddLineSegment(x1,y2,z12, x1,y1,z11);
1893  AddLineSegment(x1,y1,z11, x2,y1,z21);
1894 
1895  if (i===handle.i2-2) AddLineSegment(x2,y1,z21, x2,y2,z22);
1896  if (j===handle.j2-2) AddLineSegment(x1,y2,z12, x2,y2,z22);
1897  }
1898  }
1899  }
1900 
1901  for (var lvl=1;lvl<levels.length;++lvl)
1902  if (pos[lvl]) {
1903  if (indx[lvl] !== nfaces[lvl]*9)
1904  console.error('SURF faces missmatch lvl', lvl, 'faces', nfaces[lvl], 'index', indx[lvl], 'check', nfaces[lvl]*9 - indx[lvl]);
1905  var geometry = new THREE.BufferGeometry();
1906  geometry.addAttribute( 'position', new THREE.BufferAttribute( pos[lvl], 3 ) );
1907  geometry.computeVertexNormals();
1908  if (donormals && (lvl===1)) RecalculateNormals(geometry.getAttribute('normal').array);
1909 
1910  var fcolor, material;
1911  if (palette) {
1912  fcolor = palette.calcColor(lvl, levels.length);
1913  } else {
1914  fcolor = histo.fFillColor > 1 ? this.get_color(histo.fFillColor) : 'white';
1915  if ((this.options.Surf === 14) && (histo.fFillColor<2)) fcolor = this.get_color(48);
1916  }
1917  if (this.options.Surf === 14)
1918  material = new THREE.MeshLambertMaterial( { color: fcolor, side: THREE.DoubleSide } );
1919  else
1920  material = new THREE.MeshBasicMaterial( { color: fcolor, side: THREE.DoubleSide } );
1921 
1922  var mesh = new THREE.Mesh(geometry, material);
1923 
1924  main.toplevel.add(mesh);
1925 
1926  mesh.painter = this; // to let use it with context menu
1927  }
1928 
1929 
1930  if (lpos) {
1931  if (nsegments*6 !== lindx)
1932  console.error('SURF lines mismmatch nsegm', nsegments, ' lindx', lindx, 'difference', nsegments*6 - lindx);
1933 
1934  var lcolor = this.get_color(histo.fLineColor),
1935  material = new THREE.LineBasicMaterial({ color: new THREE.Color(lcolor) });
1936  if (!JSROOT.browser.isIE) material.linewidth = histo.fLineWidth;
1937  var line = JSROOT.Painter.createLineSegments(lpos, material);
1938  line.painter = this;
1939  main.toplevel.add(line);
1940  }
1941 
1942  if (grid) {
1943  if (ngridsegments*6 !== gindx)
1944  console.error('SURF grid draw mismatch ngridsegm', ngridsegments, 'gindx', gindx, 'diff', ngridsegments*6 - gindx);
1945 
1946  var material;
1947 
1948  if (this.options.Surf === 1)
1949  material = new THREE.LineDashedMaterial( { color: 0x0, dashSize: 2, gapSize: 2 } );
1950  else
1951  material = new THREE.LineBasicMaterial({ color: new THREE.Color(this.get_color(histo.fLineColor)) });
1952 
1953  var line = JSROOT.Painter.createLineSegments(grid, material);
1954  line.painter = this;
1955  main.toplevel.add(line);
1956  }
1957 
1958  if (this.options.Surf === 17)
1959  this.DrawContour3D();
1960 
1961  if (this.options.Surf === 13) {
1962 
1963  handle = this.PrepareColorDraw({rounding: false, use3d: true, extra: 100, middle: 0.0 });
1964 
1965  // get levels
1966  var levels = this.GetContour(), // init contour
1967  palette = this.GetPalette(),
1968  lastcolindx = -1, layerz = 2*main.size_z3d;
1969 
1970  this.BuildContour(handle, levels, palette,
1971  function(colindx,xp,yp,iminus,iplus) {
1972  // no need for duplicated point
1973  if ((xp[iplus] === xp[iminus]) && (yp[iplus] === yp[iminus])) iplus--;
1974 
1975  // ignore less than three points
1976  if (iplus - iminus < 3) return;
1977 
1978  var pnts = [];
1979 
1980  for (var i = iminus; i <= iplus; ++i)
1981  if ((i === iminus) || (xp[i] !== xp[i-1]) || (yp[i] !== yp[i-1]))
1982  pnts.push(new THREE.Vector2(xp[i], yp[i]));
1983 
1984  if (pnts.length < 3) return;
1985 
1986  var faces = THREE.ShapeUtils.triangulateShape(pnts , []);
1987 
1988  if (!faces || (faces.length === 0)) return;
1989 
1990  if ((lastcolindx < 0) || (lastcolindx !== colindx)) {
1991  lastcolindx = colindx;
1992  layerz+=0.0001*main.size_z3d; // change layers Z
1993  }
1994 
1995  var pos = new Float32Array(faces.length*9),
1996  norm = new Float32Array(faces.length*9),
1997  indx = 0;
1998 
1999  for (var n=0;n<faces.length;++n) {
2000  var face = faces[n];
2001  for (var v=0;v<3;++v) {
2002  var pnt = pnts[face[v]];
2003  pos[indx] = pnt.x;
2004  pos[indx+1] = pnt.y;
2005  pos[indx+2] = layerz;
2006  norm[indx] = 0;
2007  norm[indx+1] = 0;
2008  norm[indx+2] = 1;
2009 
2010  indx+=3;
2011  }
2012  }
2013 
2014  var geometry = new THREE.BufferGeometry();
2015  geometry.addAttribute( 'position', new THREE.BufferAttribute( pos, 3 ) );
2016  geometry.addAttribute( 'normal', new THREE.BufferAttribute( norm, 3 ) );
2017 
2018  var fcolor = palette.getColor(colindx);
2019  var material = new THREE.MeshBasicMaterial( { color: fcolor, flatShading: true, side: THREE.DoubleSide, opacity: 0.5 } );
2020  var mesh = new THREE.Mesh(geometry, material);
2021  mesh.painter = this;
2022  main.toplevel.add(mesh);
2023  }
2024  );
2025  }
2026  }
2027 
2028  JSROOT.TH2Painter.prototype.DrawError = function() {
2029  var pthis = this,
2030  main = this.frame_painter(),
2031  histo = this.GetHisto(),
2032  handle = this.PrepareColorDraw({ rounding: false, use3d: true, extra: 1 }),
2033  zmin = main.grz.domain()[0],
2034  zmax = main.grz.domain()[1],
2035  i, j, bin, binz, binerr, x1, y1, x2, y2, z1, z2,
2036  nsegments = 0, lpos = null, binindx = null, lindx = 0;
2037 
2038  function check_skip_min() {
2039  // return true if minimal histogram value should be skipped
2040  if (pthis.options.Zero || (zmin>0)) return false;
2041  return !pthis._show_empty_bins;
2042  }
2043 
2044  // loop over the points - first loop counts points, second fill arrays
2045  for (var loop=0;loop<2;++loop) {
2046 
2047  for (i=handle.i1;i<handle.i2;++i) {
2048  x1 = handle.grx[i];
2049  x2 = handle.grx[i+1];
2050  for (j=handle.j1;j<handle.j2;++j) {
2051  binz = histo.getBinContent(i+1, j+1);
2052  if ((binz < zmin) || (binz > zmax)) continue;
2053  if ((binz===zmin) && check_skip_min()) continue;
2054 
2055  // just count number of segments
2056  if (loop===0) { nsegments+=3; continue; }
2057 
2058  bin = histo.getBin(i+1,j+1);
2059  binerr = histo.getBinError(bin);
2060  binindx[lindx/18] = bin;
2061 
2062  y1 = handle.gry[j];
2063  y2 = handle.gry[j+1];
2064 
2065  z1 = main.grz((binz - binerr < zmin) ? zmin : binz-binerr);
2066  z2 = main.grz((binz + binerr > zmax) ? zmax : binz+binerr);
2067 
2068  lpos[lindx] = x1; lpos[lindx+3] = x2;
2069  lpos[lindx+1] = lpos[lindx+4] = (y1+y2)/2;
2070  lpos[lindx+2] = lpos[lindx+5] = (z1+z2)/2;
2071  lindx+=6;
2072 
2073  lpos[lindx] = lpos[lindx+3] = (x1+x2)/2;
2074  lpos[lindx+1] = y1; lpos[lindx+4] = y2;
2075  lpos[lindx+2] = lpos[lindx+5] = (z1+z2)/2;
2076  lindx+=6;
2077 
2078  lpos[lindx] = lpos[lindx+3] = (x1+x2)/2;
2079  lpos[lindx+1] = lpos[lindx+4] = (y1+y2)/2;
2080  lpos[lindx+2] = z1; lpos[lindx+5] = z2;
2081  lindx+=6;
2082  }
2083  }
2084 
2085  if (loop===0) {
2086  if (nsegments===0) return;
2087  lpos = new Float32Array(nsegments*6);
2088  binindx = new Int32Array(nsegments/3);
2089  }
2090  }
2091 
2092  // create lines
2093  var lcolor = this.get_color(this.GetObject().fLineColor),
2094  material = new THREE.LineBasicMaterial({ color: new THREE.Color(lcolor) }),
2095  line = JSROOT.Painter.createLineSegments(lpos, material);
2096 
2097  if (!JSROOT.browser.isIE) material.linewidth = this.GetObject().fLineWidth;
2098 
2099  line.painter = this;
2100  line.intersect_index = binindx;
2101  line.zmin = zmin;
2102  line.zmax = zmax;
2103  line.tip_color = (this.GetObject().fLineColor===3) ? 0xFF0000 : 0x00FF00;
2104 
2105  line.tooltip = function(intersect) {
2106  if (isNaN(intersect.index)) {
2107  console.error('segment index not provided, check three.js version', THREE.REVISION, 'expected r97');
2108  return null;
2109  }
2110 
2111  var pos = Math.floor(intersect.index / 6);
2112  if ((pos<0) || (pos >= this.intersect_index.length)) return null;
2113  var p = this.painter,
2114  histo = p.GetHisto(),
2115  main = p.frame_painter(),
2116  tip = p.Get3DToolTip(this.intersect_index[pos]);
2117 
2118  tip.x1 = Math.max(-main.size_xy3d, main.grx(histo.fXaxis.GetBinLowEdge(tip.ix)));
2119  tip.x2 = Math.min(main.size_xy3d, main.grx(histo.fXaxis.GetBinLowEdge(tip.ix+1)));
2120  tip.y1 = Math.max(-main.size_xy3d, main.gry(histo.fYaxis.GetBinLowEdge(tip.iy)));
2121  tip.y2 = Math.min(main.size_xy3d, main.gry(histo.fXaxis.GetBinLowEdge(tip.iy+1)));
2122 
2123  tip.z1 = main.grz(tip.value-tip.error < this.zmin ? this.zmin : tip.value-tip.error);
2124  tip.z2 = main.grz(tip.value+tip.error > this.zmax ? this.zmax : tip.value+tip.error);
2125 
2126  tip.color = this.tip_color;
2127 
2128  return tip;
2129  }
2130 
2131  main.toplevel.add(line);
2132  }
2133 
2134  JSROOT.TH2Painter.prototype.DrawPolyLego = function() {
2135  var histo = this.GetHisto(),
2136  pmain = this.frame_painter(),
2137  axis_zmin = pmain.grz.domain()[0],
2138  axis_zmax = pmain.grz.domain()[1],
2139  colindx, bin, i, len = histo.fBins.arr.length, cnt = 0, totalnfaces = 0,
2140  z0 = pmain.grz(axis_zmin), z1 = z0;
2141 
2142  // force recalculations of contours
2143  this.fContour = null;
2144  this.fCustomContour = false;
2145 
2146  // use global coordinates
2147  this.maxbin = this.gmaxbin;
2148  this.minbin = this.gminbin;
2149  this.minposbin = this.gminposbin;
2150 
2151  for (i = 0; i < len; ++ i) {
2152  bin = histo.fBins.arr[i];
2153  if (bin.fContent < axis_zmin) continue;
2154 
2155  colindx = this.getContourColor(bin.fContent, true);
2156  if (colindx === null) continue;
2157 
2158  // check if bin outside visible range
2159  if ((bin.fXmin > pmain.scale_xmax) || (bin.fXmax < pmain.scale_xmin) ||
2160  (bin.fYmin > pmain.scale_ymax) || (bin.fYmax < pmain.scale_ymin)) continue;
2161 
2162  z1 = pmain.grz((bin.fContent > axis_zmax) ? axis_zmax : bin.fContent);
2163 
2164  var all_pnts = [], all_faces = [],
2165  ngraphs = 1, gr = bin.fPoly, nfaces = 0;
2166 
2167  if (gr._typename=='TMultiGraph') {
2168  ngraphs = bin.fPoly.fGraphs.arr.length;
2169  gr = null;
2170  }
2171 
2172  for (var ngr = 0; ngr < ngraphs; ++ngr) {
2173  if (!gr || (ngr>0)) gr = bin.fPoly.fGraphs.arr[ngr];
2174 
2175  var npnts = gr.fNpoints, x = gr.fX, y = gr.fY;
2176  while ((npnts>2) && (x[0]===x[npnts-1]) && (y[0]===y[npnts-1])) --npnts;
2177 
2178  var pnts, faces;
2179 
2180  for (var ntry=0;ntry<2;++ntry) {
2181  // run two loops - on the first try to compress data, on second - run as is (removing duplication)
2182 
2183  var lastx, lasty, currx, curry,
2184  dist2 = pmain.size_xy3d*pmain.size_z3d,
2185  dist2limit = (ntry>0) ? 0 : dist2/1e6;
2186 
2187  pnts = []; faces = null;
2188 
2189  for (var vert = 0; vert < npnts; ++vert) {
2190  currx = pmain.grx(x[vert]);
2191  curry = pmain.gry(y[vert]);
2192  if (vert>0)
2193  dist2 = (currx-lastx)*(currx-lastx) + (curry-lasty)*(curry-lasty);
2194  if (dist2 > dist2limit) {
2195  pnts.push(new THREE.Vector2(currx, curry));
2196  lastx = currx;
2197  lasty = curry;
2198  }
2199  }
2200 
2201  try {
2202  if (pnts.length > 2)
2203  faces = THREE.ShapeUtils.triangulateShape(pnts , []);
2204  } catch(e) {
2205  faces = null;
2206  }
2207 
2208  if (faces && (faces.length>pnts.length-3)) break;
2209  }
2210 
2211  if (faces && faces.length && pnts) {
2212  all_pnts.push(pnts);
2213  all_faces.push(faces);
2214 
2215  nfaces += faces.length * 2;
2216  if (z1>z0) nfaces += pnts.length*2;
2217  }
2218  }
2219 
2220  var pos = new Float32Array(nfaces*9), indx = 0;
2221 
2222  for (var ngr=0;ngr<all_pnts.length;++ngr) {
2223  var pnts = all_pnts[ngr], faces = all_faces[ngr];
2224 
2225  for (var layer=0;layer<2;++layer) {
2226  for (var n=0;n<faces.length;++n) {
2227  var face = faces[n],
2228  pnt1 = pnts[face[0]],
2229  pnt2 = pnts[face[(layer===0) ? 2 : 1]],
2230  pnt3 = pnts[face[(layer===0) ? 1 : 2]];
2231 
2232  pos[indx] = pnt1.x;
2233  pos[indx+1] = pnt1.y;
2234  pos[indx+2] = layer ? z1 : z0;
2235  indx+=3;
2236 
2237  pos[indx] = pnt2.x;
2238  pos[indx+1] = pnt2.y;
2239  pos[indx+2] = layer ? z1 : z0;
2240  indx+=3;
2241 
2242  pos[indx] = pnt3.x;
2243  pos[indx+1] = pnt3.y;
2244  pos[indx+2] = layer ? z1 : z0;
2245  indx+=3;
2246  }
2247  }
2248 
2249  if (z1>z0) {
2250  for (var n=0;n<pnts.length;++n) {
2251  var pnt1 = pnts[n],
2252  pnt2 = pnts[(n>0) ? n-1 : pnts.length-1];
2253 
2254  pos[indx] = pnt1.x;
2255  pos[indx+1] = pnt1.y;
2256  pos[indx+2] = z0;
2257  indx+=3;
2258 
2259  pos[indx] = pnt2.x;
2260  pos[indx+1] = pnt2.y;
2261  pos[indx+2] = z0;
2262  indx+=3;
2263 
2264  pos[indx] = pnt2.x;
2265  pos[indx+1] = pnt2.y;
2266  pos[indx+2] = z1;
2267  indx+=3;
2268 
2269  pos[indx] = pnt1.x;
2270  pos[indx+1] = pnt1.y;
2271  pos[indx+2] = z0;
2272  indx+=3;
2273 
2274  pos[indx] = pnt2.x;
2275  pos[indx+1] = pnt2.y;
2276  pos[indx+2] = z1;
2277  indx+=3;
2278 
2279  pos[indx] = pnt1.x;
2280  pos[indx+1] = pnt1.y;
2281  pos[indx+2] = z1;
2282  indx+=3;
2283  }
2284  }
2285  }
2286 
2287  var geometry = new THREE.BufferGeometry();
2288  geometry.addAttribute( 'position', new THREE.BufferAttribute( pos, 3 ) );
2289  geometry.computeVertexNormals();
2290 
2291  var fcolor = this.fPalette.getColor(colindx);
2292  var material = new THREE.MeshBasicMaterial( { color: fcolor, flatShading: true } );
2293  var mesh = new THREE.Mesh(geometry, material);
2294 
2295  pmain.toplevel.add(mesh);
2296 
2297  mesh.painter = this;
2298  mesh.bins_index = i;
2299  mesh.draw_z0 = z0;
2300  mesh.draw_z1 = z1;
2301  mesh.tip_color = 0x00FF00;
2302 
2303  mesh.tooltip = function(intersects) {
2304 
2305  var p = this.painter, main = p.frame_painter(),
2306  bin = p.GetObject().fBins.arr[this.bins_index];
2307 
2308  var tip = {
2309  use_itself: true, // indicate that use mesh itself for highlighting
2310  x1: main.grx(bin.fXmin),
2311  x2: main.grx(bin.fXmax),
2312  y1: main.gry(bin.fYmin),
2313  y2: main.gry(bin.fYmax),
2314  z1: this.draw_z0,
2315  z2: this.draw_z1,
2316  bin: this.bins_index,
2317  value: bin.fContent,
2318  color: this.tip_color,
2319  lines: p.ProvidePolyBinHints(this.bins_index)
2320  };
2321 
2322  return tip;
2323  };
2324 
2325  totalnfaces += nfaces;
2326  cnt++;
2327  }
2328  }
2329 
2330  // ==============================================================================
2331 
2332  function TH3Painter(histo) {
2333  JSROOT.THistPainter.call(this, histo);
2334 
2335  this.mode3d = true;
2336  }
2337 
2338  TH3Painter.prototype = Object.create(JSROOT.THistPainter.prototype);
2339 
2340  TH3Painter.prototype.ScanContent = function(when_axis_changed) {
2341 
2342  // no need to rescan histogram while result does not depend from axis selection
2343  if (when_axis_changed && this.nbinsx && this.nbinsy && this.nbinsz) return;
2344 
2345  var histo = this.GetObject();
2346 
2347  this.nbinsx = histo.fXaxis.fNbins;
2348  this.nbinsy = histo.fYaxis.fNbins;
2349  this.nbinsz = histo.fZaxis.fNbins;
2350 
2351  this.xmin = histo.fXaxis.fXmin;
2352  this.xmax = histo.fXaxis.fXmax;
2353 
2354  this.ymin = histo.fYaxis.fXmin;
2355  this.ymax = histo.fYaxis.fXmax;
2356 
2357  this.zmin = histo.fZaxis.fXmin;
2358  this.zmax = histo.fZaxis.fXmax;
2359 
2360  // global min/max, used at the moment in 3D drawing
2361 
2362  this.gminbin = this.gmaxbin = histo.getBinContent(1,1,1);
2363 
2364  for (var i = 0; i < this.nbinsx; ++i)
2365  for (var j = 0; j < this.nbinsy; ++j)
2366  for (var k = 0; k < this.nbinsz; ++k) {
2367  var bin_content = histo.getBinContent(i+1, j+1, k+1);
2368  if (bin_content < this.gminbin) this.gminbin = bin_content; else
2369  if (bin_content > this.gmaxbin) this.gmaxbin = bin_content;
2370  }
2371 
2372  this.draw_content = this.gmaxbin > 0;
2373 
2374  this.CreateAxisFuncs(true, true);
2375  }
2376 
2377  TH3Painter.prototype.CountStat = function() {
2378  var histo = this.GetHisto(), xaxis = histo.fXaxis, yaxis = histo.fYaxis, zaxis = histo.fZaxis,
2379  stat_sum0 = 0, stat_sumx1 = 0, stat_sumy1 = 0,
2380  stat_sumz1 = 0, stat_sumx2 = 0, stat_sumy2 = 0, stat_sumz2 = 0,
2381  i1 = this.GetSelectIndex("x", "left"),
2382  i2 = this.GetSelectIndex("x", "right"),
2383  j1 = this.GetSelectIndex("y", "left"),
2384  j2 = this.GetSelectIndex("y", "right"),
2385  k1 = this.GetSelectIndex("z", "left"),
2386  k2 = this.GetSelectIndex("z", "right"),
2387  fp = this.frame_painter(),
2388  res = { name: histo.fName, entries: 0, integral: 0, meanx: 0, meany: 0, meanz: 0, rmsx: 0, rmsy: 0, rmsz: 0 },
2389  xi, yi, zi, xx, xside, yy, yside, zz, zside, cont;
2390 
2391  for (xi = 0; xi < this.nbinsx+2; ++xi) {
2392 
2393  xx = xaxis.GetBinCoord(xi - 0.5);
2394  xside = (xi < i1) ? 0 : (xi > i2 ? 2 : 1);
2395 
2396  for (yi = 0; yi < this.nbinsy+2; ++yi) {
2397 
2398  yy = yaxis.GetBinCoord(yi - 0.5);
2399  yside = (yi < j1) ? 0 : (yi > j2 ? 2 : 1);
2400 
2401  for (zi = 0; zi < this.nbinsz+2; ++zi) {
2402 
2403  zz = zaxis.GetBinCoord(zi - 0.5);
2404  zside = (zi < k1) ? 0 : (zi > k2 ? 2 : 1);
2405 
2406  cont = histo.getBinContent(xi, yi, zi);
2407  res.entries += cont;
2408 
2409  if ((xside==1) && (yside==1) && (zside==1)) {
2410  stat_sum0 += cont;
2411  stat_sumx1 += xx * cont;
2412  stat_sumy1 += yy * cont;
2413  stat_sumz1 += zz * cont;
2414  stat_sumx2 += xx * xx * cont;
2415  stat_sumy2 += yy * yy * cont;
2416  stat_sumz2 += zz * zz * cont;
2417  }
2418  }
2419  }
2420  }
2421 
2422  if ((histo.fTsumw > 0) && !fp.IsAxisZoomed("x") && !fp.IsAxisZoomed("y") && !fp.IsAxisZoomed("z")) {
2423  stat_sum0 = histo.fTsumw;
2424  stat_sumx1 = histo.fTsumwx;
2425  stat_sumx2 = histo.fTsumwx2;
2426  stat_sumy1 = histo.fTsumwy;
2427  stat_sumy2 = histo.fTsumwy2;
2428  stat_sumz1 = histo.fTsumwz;
2429  stat_sumz2 = histo.fTsumwz2;
2430  }
2431 
2432  if (stat_sum0 > 0) {
2433  res.meanx = stat_sumx1 / stat_sum0;
2434  res.meany = stat_sumy1 / stat_sum0;
2435  res.meanz = stat_sumz1 / stat_sum0;
2436  res.rmsx = Math.sqrt(Math.abs(stat_sumx2 / stat_sum0 - res.meanx * res.meanx));
2437  res.rmsy = Math.sqrt(Math.abs(stat_sumy2 / stat_sum0 - res.meany * res.meany));
2438  res.rmsz = Math.sqrt(Math.abs(stat_sumz2 / stat_sum0 - res.meanz * res.meanz));
2439  }
2440 
2441  res.integral = stat_sum0;
2442 
2443  if (histo.fEntries > 1) res.entries = histo.fEntries;
2444 
2445  return res;
2446  }
2447 
2448  TH3Painter.prototype.FillStatistic = function(stat, dostat, dofit) {
2449 
2450  // no need to refill statistic if histogram is dummy
2451  if (this.IgnoreStatsFill()) return false;
2452 
2453  var data = this.CountStat(),
2454  print_name = dostat % 10,
2455  print_entries = Math.floor(dostat / 10) % 10,
2456  print_mean = Math.floor(dostat / 100) % 10,
2457  print_rms = Math.floor(dostat / 1000) % 10,
2458  print_under = Math.floor(dostat / 10000) % 10,
2459  print_over = Math.floor(dostat / 100000) % 10,
2460  print_integral = Math.floor(dostat / 1000000) % 10;
2461  //var print_skew = Math.floor(dostat / 10000000) % 10;
2462  //var print_kurt = Math.floor(dostat / 100000000) % 10;
2463 
2464  stat.ClearPave();
2465 
2466  if (print_name > 0)
2467  stat.AddText(data.name);
2468 
2469  if (print_entries > 0)
2470  stat.AddText("Entries = " + stat.Format(data.entries,"entries"));
2471 
2472  if (print_mean > 0) {
2473  stat.AddText("Mean x = " + stat.Format(data.meanx));
2474  stat.AddText("Mean y = " + stat.Format(data.meany));
2475  stat.AddText("Mean z = " + stat.Format(data.meanz));
2476  }
2477 
2478  if (print_rms > 0) {
2479  stat.AddText("Std Dev x = " + stat.Format(data.rmsx));
2480  stat.AddText("Std Dev y = " + stat.Format(data.rmsy));
2481  stat.AddText("Std Dev z = " + stat.Format(data.rmsz));
2482  }
2483 
2484  if (print_integral > 0) {
2485  stat.AddText("Integral = " + stat.Format(data.integral,"entries"));
2486  }
2487 
2488  if (dofit) stat.FillFunctionStat(this.FindFunction('TF1'), dofit);
2489 
2490  return true;
2491  }
2492 
2493  TH3Painter.prototype.GetBinTips = function (ix, iy, iz) {
2494  var lines = [], pmain = this.frame_painter(), histo = this.GetHisto();
2495 
2496  lines.push(this.GetTipName());
2497 
2498  if (pmain.x_kind == 'labels')
2499  lines.push("x = " + pmain.AxisAsText("x", histo.fXaxis.GetBinLowEdge(ix+1)) + " xbin=" + (ix+1));
2500  else
2501  lines.push("x = [" + pmain.AxisAsText("x", histo.fXaxis.GetBinLowEdge(ix+1)) + ", " + pmain.AxisAsText("x", histo.fXaxis.GetBinLowEdge(ix+2)) + ") xbin=" + (ix+1));
2502 
2503  if (pmain.y_kind == 'labels')
2504  lines.push("y = " + pmain.AxisAsText("y", histo.fYaxis.GetBinLowEdge(iy+1)) + " ybin=" + (iy+1));
2505  else
2506  lines.push("y = [" + pmain.AxisAsText("y", histo.fYaxis.GetBinLowEdge(iy+1)) + ", " + pmain.AxisAsText("y", histo.fYaxis.GetBinLowEdge(iy+2)) + ") ybin=" + (iy+1));
2507 
2508  if (pmain.z_kind == 'labels')
2509  lines.push("z = " + pmain.AxisAsText("z", histo.fZaxis.GetBinLowEdge(iz+1)) + " zbin=" + (iz+1));
2510  else
2511  lines.push("z = [" + pmain.AxisAsText("z", histo.fZaxis.GetBinLowEdge(iz+1)) + ", " + pmain.AxisAsText("z", histo.fZaxis.GetBinLowEdge(iz+2)) + ") zbin=" + (iz+1));
2512 
2513  var binz = histo.getBinContent(ix+1, iy+1, iz+1);
2514  if (binz === Math.round(binz))
2515  lines.push("entries = " + binz);
2516  else
2517  lines.push("entries = " + JSROOT.FFormat(binz, JSROOT.gStyle.fStatFormat));
2518 
2519  return lines;
2520  }
2521 
2522  TH3Painter.prototype.Draw3DScatter = function() {
2523  // try to draw 3D histogram as scatter plot
2524  // if too many points, box will be displayed
2525 
2526  var histo = this.GetObject(),
2527  main = this.frame_painter(),
2528  i1 = this.GetSelectIndex("x", "left", 0.5),
2529  i2 = this.GetSelectIndex("x", "right", 0),
2530  j1 = this.GetSelectIndex("y", "left", 0.5),
2531  j2 = this.GetSelectIndex("y", "right", 0),
2532  k1 = this.GetSelectIndex("z", "left", 0.5),
2533  k2 = this.GetSelectIndex("z", "right", 0),
2534  name = this.GetTipName("<br/>"),
2535  i, j, k, bin_content;
2536 
2537  if ((i2<=i1) || (j2<=j1) || (k2<=k1)) return true;
2538 
2539  // scale down factor if too large values
2540  var coef = (this.gmaxbin > 1000) ? 1000/this.gmaxbin : 1,
2541  numpixels = 0, sumz = 0, content_lmt = Math.max(0, this.gminbin);
2542 
2543  for (i = i1; i < i2; ++i) {
2544  for (j = j1; j < j2; ++j) {
2545  for (k = k1; k < k2; ++k) {
2546  bin_content = histo.getBinContent(i+1, j+1, k+1);
2547  sumz += bin_content;
2548  if (bin_content <= content_lmt) continue;
2549  numpixels += Math.round(bin_content*coef);
2550  }
2551  }
2552  }
2553 
2554  // too many pixels - use box drawing
2555  if (numpixels > (main.webgl ? 100000 : 30000)) return false;
2556 
2557  JSROOT.seed(sumz);
2558 
2559  var pnts = new JSROOT.Painter.PointsCreator(numpixels, main.webgl, main.size_xy3d/200),
2560  bins = new Int32Array(numpixels), nbin = 0;
2561 
2562  for (i = i1; i < i2; ++i) {
2563  for (j = j1; j < j2; ++j) {
2564  for (k = k1; k < k2; ++k) {
2565  bin_content = histo.getBinContent(i+1, j+1, k+1);
2566  if (bin_content <= content_lmt) continue;
2567  var num = Math.round(bin_content*coef);
2568 
2569  for (var n=0;n<num;++n) {
2570  var binx = histo.fXaxis.GetBinCoord(i+JSROOT.random()),
2571  biny = histo.fYaxis.GetBinCoord(j+JSROOT.random()),
2572  binz = histo.fZaxis.GetBinCoord(k+JSROOT.random());
2573 
2574  // remember bin index for tooltip
2575  bins[nbin++] = histo.getBin(i+1, j+1, k+1);
2576 
2577  pnts.AddPoint(main.grx(binx), main.gry(biny), main.grz(binz));
2578  }
2579  }
2580  }
2581  }
2582 
2583  var mesh = pnts.CreatePoints(this.get_color(histo.fMarkerColor));
2584  main.toplevel.add(mesh);
2585 
2586  mesh.bins = bins;
2587  mesh.painter = this;
2588  mesh.tip_color = (histo.fMarkerColor===3) ? 0xFF0000 : 0x00FF00;
2589 
2590  mesh.tooltip = function(intersect) {
2591  if (isNaN(intersect.index)) {
2592  console.error('intersect.index not provided, check three.js version', THREE.REVISION, 'expected r97');
2593  return null;
2594  }
2595 
2596  var indx = Math.floor(intersect.index / this.nvertex);
2597  if ((indx<0) || (indx >= this.bins.length)) return null;
2598 
2599  var p = this.painter, histo = p.GetHisto(),
2600  main = p.frame_painter(),
2601  tip = p.Get3DToolTip(this.bins[indx]);
2602 
2603  tip.x1 = main.grx(histo.fXaxis.GetBinLowEdge(tip.ix));
2604  tip.x2 = main.grx(histo.fXaxis.GetBinLowEdge(tip.ix+1));
2605  tip.y1 = main.gry(histo.fYaxis.GetBinLowEdge(tip.iy));
2606  tip.y2 = main.gry(histo.fYaxis.GetBinLowEdge(tip.iy+1));
2607  tip.z1 = main.grz(histo.fZaxis.GetBinLowEdge(tip.iz));
2608  tip.z2 = main.grz(histo.fZaxis.GetBinLowEdge(tip.iz+1));
2609  tip.color = this.tip_color;
2610  tip.opacity = 0.3;
2611 
2612  return tip;
2613  }
2614 
2615  return true;
2616  }
2617 
2618  TH3Painter.prototype.Draw3DBins = function() {
2619 
2620  if (!this.draw_content) return;
2621 
2622  if (!this.options.Box && !this.options.GLBox && !this.options.GLColor && !this.options.Lego)
2623  if (this.Draw3DScatter()) return;
2624 
2625  var rootcolor = this.GetObject().fFillColor,
2626  fillcolor = this.get_color(rootcolor),
2627  main = this.frame_painter(),
2628  buffer_size = 0, use_lambert = false,
2629  use_helper = false, use_colors = false, use_opacity = 1, use_scale = true,
2630  single_bin_verts, single_bin_norms,
2631  box_option = this.options.Box ? this.options.BoxStyle : 0,
2632  tipscale = 0.5;
2633 
2634  if (!box_option && this.options.Lego) box_option = (this.options.Lego===1) ? 10 : this.options.Lego;
2635 
2636  if ((this.options.GLBox === 11) || (this.options.GLBox === 12)) {
2637 
2638  tipscale = 0.4;
2639  use_lambert = true;
2640  if (this.options.GLBox === 12) use_colors = true;
2641 
2642  var geom = JSROOT.Painter.TestWebGL() ? new THREE.SphereGeometry(0.5, 16, 12) : new THREE.SphereGeometry(0.5, 8, 6);
2643  geom.applyMatrix( new THREE.Matrix4().makeRotationX( Math.PI / 2 ) );
2644 
2645  buffer_size = geom.faces.length*9;
2646  single_bin_verts = new Float32Array(buffer_size);
2647  single_bin_norms = new Float32Array(buffer_size);
2648 
2649  // Fill a typed array with cube geometry that will be shared by all
2650  // (This technically could be put into an InstancedBufferGeometry but
2651  // performance gain is likely not huge )
2652  for (var face = 0; face < geom.faces.length; ++face) {
2653  single_bin_verts[9*face ] = geom.vertices[geom.faces[face].a].x;
2654  single_bin_verts[9*face+1] = geom.vertices[geom.faces[face].a].y;
2655  single_bin_verts[9*face+2] = geom.vertices[geom.faces[face].a].z;
2656  single_bin_verts[9*face+3] = geom.vertices[geom.faces[face].b].x;
2657  single_bin_verts[9*face+4] = geom.vertices[geom.faces[face].b].y;
2658  single_bin_verts[9*face+5] = geom.vertices[geom.faces[face].b].z;
2659  single_bin_verts[9*face+6] = geom.vertices[geom.faces[face].c].x;
2660  single_bin_verts[9*face+7] = geom.vertices[geom.faces[face].c].y;
2661  single_bin_verts[9*face+8] = geom.vertices[geom.faces[face].c].z;
2662 
2663  single_bin_norms[9*face ] = geom.faces[face].vertexNormals[0].x;
2664  single_bin_norms[9*face+1] = geom.faces[face].vertexNormals[0].y;
2665  single_bin_norms[9*face+2] = geom.faces[face].vertexNormals[0].z;
2666  single_bin_norms[9*face+3] = geom.faces[face].vertexNormals[1].x;
2667  single_bin_norms[9*face+4] = geom.faces[face].vertexNormals[1].y;
2668  single_bin_norms[9*face+5] = geom.faces[face].vertexNormals[1].z;
2669  single_bin_norms[9*face+6] = geom.faces[face].vertexNormals[2].x;
2670  single_bin_norms[9*face+7] = geom.faces[face].vertexNormals[2].y;
2671  single_bin_norms[9*face+8] = geom.faces[face].vertexNormals[2].z;
2672  }
2673 
2674  } else {
2675 
2676  var indicies = JSROOT.Painter.Box3D.Indexes,
2677  normals = JSROOT.Painter.Box3D.Normals,
2678  vertices = JSROOT.Painter.Box3D.Vertices;
2679 
2680  buffer_size = indicies.length*3;
2681  single_bin_verts = new Float32Array(buffer_size);
2682  single_bin_norms = new Float32Array(buffer_size);
2683 
2684  for (var k=0,nn=-3;k<indicies.length;++k) {
2685  var vert = vertices[indicies[k]];
2686  single_bin_verts[k*3] = vert.x-0.5;
2687  single_bin_verts[k*3+1] = vert.y-0.5;
2688  single_bin_verts[k*3+2] = vert.z-0.5;
2689 
2690  if (k%6===0) nn+=3;
2691  single_bin_norms[k*3] = normals[nn];
2692  single_bin_norms[k*3+1] = normals[nn+1];
2693  single_bin_norms[k*3+2] = normals[nn+2];
2694  }
2695  use_helper = true;
2696 
2697  if (box_option===12) { use_colors = true; } else
2698  if (box_option===13) { use_colors = true; use_helper = false; } else
2699  if (this.options.GLColor) { use_colors = true; use_opacity = 0.5; use_scale = false; use_helper = false; use_lambert = true; }
2700  }
2701 
2702  if (use_scale)
2703  use_scale = (this.gminbin || this.gmaxbin) ? 1 / Math.max(Math.abs(this.gminbin), Math.abs(this.gmaxbin)) : 1;
2704 
2705  var histo = this.GetHisto(),
2706  i1 = this.GetSelectIndex("x", "left", 0.5),
2707  i2 = this.GetSelectIndex("x", "right", 0),
2708  j1 = this.GetSelectIndex("y", "left", 0.5),
2709  j2 = this.GetSelectIndex("y", "right", 0),
2710  k1 = this.GetSelectIndex("z", "left", 0.5),
2711  k2 = this.GetSelectIndex("z", "right", 0),
2712  name = this.GetTipName("<br/>");
2713 
2714  if ((i2<=i1) || (j2<=j1) || (k2<=k1)) return;
2715 
2716  var scalex = (main.grx(histo.fXaxis.GetBinLowEdge(i2+1)) - main.grx(histo.fXaxis.GetBinLowEdge(i1+1))) / (i2-i1),
2717  scaley = (main.gry(histo.fYaxis.GetBinLowEdge(j2+1)) - main.gry(histo.fYaxis.GetBinLowEdge(j1+1))) / (j2-j1),
2718  scalez = (main.grz(histo.fZaxis.GetBinLowEdge(k2+1)) - main.grz(histo.fZaxis.GetBinLowEdge(k1+1))) / (k2-k1);
2719 
2720  var nbins = 0, i, j, k, wei, bin_content, cols_size = [], num_colors = 0, cols_sequence = [];
2721 
2722  for (i = i1; i < i2; ++i) {
2723  for (j = j1; j < j2; ++j) {
2724  for (k = k1; k < k2; ++k) {
2725  bin_content = histo.getBinContent(i+1, j+1, k+1);
2726  if ((bin_content===0) || (bin_content < this.gminbin)) continue;
2727  wei = use_scale ? Math.pow(Math.abs(bin_content*use_scale), 0.3333) : 1;
2728  if (wei < 1e-3) continue; // do not draw empty or very small bins
2729 
2730  nbins++;
2731 
2732  if (!use_colors) continue;
2733 
2734  var colindx = this.getContourColor(bin_content, true);
2735  if (colindx !== null) {
2736  if (cols_size[colindx] === undefined) {
2737  cols_size[colindx] = 0;
2738  cols_sequence[colindx] = num_colors++;
2739  }
2740  cols_size[colindx]+=1;
2741  } else {
2742  console.error('not found color for', bin_content);
2743  }
2744  }
2745  }
2746  }
2747 
2748  if (!use_colors) {
2749  cols_size.push(nbins);
2750  num_colors = 1;
2751  cols_sequence = [0];
2752  }
2753 
2754  var cols_nbins = new Array(num_colors),
2755  bin_verts = new Array(num_colors),
2756  bin_norms = new Array(num_colors),
2757  bin_tooltips = new Array(num_colors),
2758  helper_kind = new Array(num_colors),
2759  helper_indexes = new Array(num_colors), // helper_kind == 1, use original vertices
2760  helper_positions = new Array(num_colors); // helper_kind == 2, all vertices copied into separate buffer
2761 
2762  for(var ncol=0;ncol<cols_size.length;++ncol) {
2763  if (!cols_size[ncol]) continue; // ignore dummy colors
2764 
2765  nbins = cols_size[ncol]; // how many bins with specified color
2766  var nseq = cols_sequence[ncol];
2767 
2768  cols_nbins[nseq] = 0; // counter for the filled bins
2769 
2770  helper_kind[nseq] = 0;
2771 
2772  // 1 - use same vertices to create helper, one can use maximal 64K vertices
2773  // 2 - all vertices copied into separate buffer
2774  if (use_helper)
2775  helper_kind[nseq] = (nbins * buffer_size / 3 > 0xFFF0) ? 2 : 1;
2776 
2777  bin_verts[nseq] = new Float32Array(nbins * buffer_size);
2778  bin_norms[nseq] = new Float32Array(nbins * buffer_size);
2779  bin_tooltips[nseq] = new Int32Array(nbins);
2780 
2781  if (helper_kind[nseq]===1)
2782  helper_indexes[nseq] = new Uint16Array(nbins * JSROOT.Painter.Box3D.MeshSegments.length);
2783 
2784  if (helper_kind[nseq]===2)
2785  helper_positions[nseq] = new Float32Array(nbins * JSROOT.Painter.Box3D.Segments.length * 3);
2786  }
2787 
2788  var binx, grx, biny, gry, binz, grz;
2789 
2790  for (i = i1; i < i2; ++i) {
2791  binx = histo.fXaxis.GetBinCenter(i+1); grx = main.grx(binx);
2792  for (j = j1; j < j2; ++j) {
2793  biny = histo.fYaxis.GetBinCenter(j+1); gry = main.gry(biny);
2794  for (k = k1; k < k2; ++k) {
2795  bin_content = histo.getBinContent(i+1, j+1, k+1);
2796  if ((bin_content===0) || (bin_content < this.gminbin)) continue;
2797 
2798  wei = use_scale ? Math.pow(Math.abs(bin_content*use_scale), 0.3333) : 1;
2799  if (wei < 1e-3) continue; // do not show very small bins
2800 
2801  var nseq = 0;
2802  if (use_colors) {
2803  var colindx = this.getContourColor(bin_content, true);
2804  if (colindx === null) continue;
2805  nseq = cols_sequence[colindx];
2806  }
2807 
2808  nbins = cols_nbins[nseq];
2809 
2810  binz = histo.fZaxis.GetBinCenter(k+1); grz = main.grz(binz);
2811 
2812  // remember bin index for tooltip
2813  bin_tooltips[nseq][nbins] = histo.getBin(i+1, j+1, k+1);
2814 
2815  var vvv = nbins * buffer_size, bin_v = bin_verts[nseq], bin_n = bin_norms[nseq];
2816 
2817  // Grab the coordinates and scale that are being assigned to each bin
2818  for (var vi = 0; vi < buffer_size; vi+=3, vvv+=3) {
2819  bin_v[vvv] = grx + single_bin_verts[vi]*scalex*wei;
2820  bin_v[vvv+1] = gry + single_bin_verts[vi+1]*scaley*wei;
2821  bin_v[vvv+2] = grz + single_bin_verts[vi+2]*scalez*wei;
2822 
2823  bin_n[vvv] = single_bin_norms[vi];
2824  bin_n[vvv+1] = single_bin_norms[vi+1];
2825  bin_n[vvv+2] = single_bin_norms[vi+2];
2826  }
2827 
2828  if (helper_kind[nseq]===1) {
2829  // reuse vertices created for the mesh
2830  var helper_segments = JSROOT.Painter.Box3D.MeshSegments;
2831  vvv = nbins * helper_segments.length;
2832  var shift = Math.round(nbins * buffer_size/3),
2833  helper_i = helper_indexes[nseq];
2834  for (var n=0;n<helper_segments.length;++n)
2835  helper_i[vvv+n] = shift + helper_segments[n];
2836  }
2837 
2838  if (helper_kind[nseq]===2) {
2839  var helper_segments = JSROOT.Painter.Box3D.Segments,
2840  helper_p = helper_positions[nseq];
2841  vvv = nbins * helper_segments.length * 3;
2842  for (var n=0;n<helper_segments.length;++n, vvv+=3) {
2843  var vert = JSROOT.Painter.Box3D.Vertices[helper_segments[n]];
2844  helper_p[vvv] = grx + (vert.x-0.5)*scalex*wei;
2845  helper_p[vvv+1] = gry + (vert.y-0.5)*scaley*wei;
2846  helper_p[vvv+2] = grz + (vert.z-0.5)*scalez*wei;
2847  }
2848  }
2849 
2850  cols_nbins[nseq] = nbins+1;
2851  }
2852  }
2853  }
2854 
2855  for(var ncol=0;ncol<cols_size.length;++ncol) {
2856  if (!cols_size[ncol]) continue; // ignore dummy colors
2857 
2858  nbins = cols_size[ncol]; // how many bins with specified color
2859  var nseq = cols_sequence[ncol];
2860 
2861  // BufferGeometries that store geometry of all bins
2862  var all_bins_buffgeom = new THREE.BufferGeometry();
2863 
2864  // Create mesh from bin buffergeometry
2865  all_bins_buffgeom.addAttribute('position', new THREE.BufferAttribute( bin_verts[nseq], 3 ) );
2866  all_bins_buffgeom.addAttribute('normal', new THREE.BufferAttribute( bin_norms[nseq], 3 ) );
2867 
2868  if (use_colors) fillcolor = this.fPalette.getColor(ncol);
2869 
2870  var material = use_lambert ? new THREE.MeshLambertMaterial({ color: fillcolor, opacity: use_opacity, transparent: (use_opacity<1) })
2871  : new THREE.MeshBasicMaterial({ color: fillcolor, opacity: use_opacity });
2872 
2873  var combined_bins = new THREE.Mesh(all_bins_buffgeom, material);
2874 
2875  combined_bins.bins = bin_tooltips[nseq];
2876  combined_bins.bins_faces = buffer_size/9;
2877  combined_bins.painter = this;
2878 
2879  combined_bins.scalex = tipscale*scalex;
2880  combined_bins.scaley = tipscale*scaley;
2881  combined_bins.scalez = tipscale*scalez;
2882  combined_bins.tip_color = (rootcolor===3) ? 0xFF0000 : 0x00FF00;
2883  combined_bins.use_scale = use_scale;
2884 
2885  combined_bins.tooltip = function(intersect) {
2886  if (isNaN(intersect.faceIndex)) {
2887  console.error('intersect.faceIndex not provided, check three.js version', THREE.REVISION, 'expected r97');
2888  return null;
2889  }
2890  var indx = Math.floor(intersect.faceIndex / this.bins_faces);
2891  if ((indx<0) || (indx >= this.bins.length)) return null;
2892 
2893  var p = this.painter,
2894  histo = p.GetHisto(),
2895  main = p.frame_painter(),
2896  tip = p.Get3DToolTip(this.bins[indx]),
2897  grx = main.grx(histo.fXaxis.GetBinCoord(tip.ix-0.5)),
2898  gry = main.gry(histo.fYaxis.GetBinCoord(tip.iy-0.5)),
2899  grz = main.grz(histo.fZaxis.GetBinCoord(tip.iz-0.5)),
2900  wei = this.use_scale ? Math.pow(Math.abs(tip.value*this.use_scale), 0.3333) : 1;
2901 
2902  tip.x1 = grx - this.scalex*wei; tip.x2 = grx + this.scalex*wei;
2903  tip.y1 = gry - this.scaley*wei; tip.y2 = gry + this.scaley*wei;
2904  tip.z1 = grz - this.scalez*wei; tip.z2 = grz + this.scalez*wei;
2905 
2906  tip.color = this.tip_color;
2907 
2908  return tip;
2909  }
2910 
2911  main.toplevel.add(combined_bins);
2912 
2913  if (helper_kind[nseq] > 0) {
2914  var lcolor = this.get_color(this.GetObject().fLineColor),
2915  helper_material = new THREE.LineBasicMaterial( { color: lcolor } ),
2916  lines = null;
2917 
2918  if (helper_kind[nseq] === 1) {
2919  // reuse positions from the mesh - only special index was created
2920  lines = JSROOT.Painter.createLineSegments( bin_verts[nseq], helper_material, helper_indexes[nseq] );
2921  } else {
2922  lines = JSROOT.Painter.createLineSegments( helper_positions[nseq], helper_material );
2923  }
2924 
2925  main.toplevel.add(lines);
2926  }
2927  }
2928  }
2929 
2930  TH3Painter.prototype.Redraw = function(resize) {
2931 
2932  var main = this.frame_painter(), // who makes axis and 3D drawing
2933  histo = this.GetHisto();
2934 
2935  if (resize) {
2936 
2937  if (main.Resize3D()) main.Render3D();
2938 
2939  } else {
2940 
2941  main.Create3DScene();
2942  main.SetAxesRanges(histo.fXaxis, this.xmin, this.xmax, histo.fYaxis, this.ymin, this.ymax, histo.fZaxis, this.zmin, this.zmax);
2943  main.Set3DOptions(this.options);
2944  main.DrawXYZ(main.toplevel, { zoom: JSROOT.gStyle.Zooming, ndim: 3 });
2945  this.Draw3DBins();
2946  main.Render3D();
2947  this.UpdateStatWebCanvas();
2948  main.AddKeysHandler();
2949  }
2950 
2951  this.DrawTitle();
2952  }
2953 
2954  TH3Painter.prototype.FillToolbar = function() {
2955  var pp = this.pad_painter();
2956  if (!pp) return;
2957 
2958  pp.AddButton(JSROOT.ToolbarIcons.auto_zoom, 'Unzoom all axes', 'ToggleZoom', "Ctrl *");
2959  if (this.draw_content)
2960  pp.AddButton(JSROOT.ToolbarIcons.statbox, 'Toggle stat box', "ToggleStatBox");
2961  pp.ShowButtons();
2962  }
2963 
2964  TH3Painter.prototype.CanZoomIn = function(axis,min,max) {
2965  // check if it makes sense to zoom inside specified axis range
2966  var obj = this.GetHisto();
2967  if (obj) obj = obj["f"+axis.toUpperCase()+"axis"];
2968  return !obj || (obj.FindBin(max,0.5) - obj.FindBin(min,0) > 1);
2969  }
2970 
2971  TH3Painter.prototype.AutoZoom = function() {
2972  var i1 = this.GetSelectIndex("x", "left"),
2973  i2 = this.GetSelectIndex("x", "right"),
2974  j1 = this.GetSelectIndex("y", "left"),
2975  j2 = this.GetSelectIndex("y", "right"),
2976  k1 = this.GetSelectIndex("z", "left"),
2977  k2 = this.GetSelectIndex("z", "right"),
2978  i,j,k, histo = this.GetObject();
2979 
2980  if ((i1 === i2) || (j1 === j2) || (k1 === k2)) return;
2981 
2982  // first find minimum
2983  var min = histo.getBinContent(i1 + 1, j1 + 1, k1+1);
2984  for (i = i1; i < i2; ++i)
2985  for (j = j1; j < j2; ++j)
2986  for (k = k1; k < k2; ++k)
2987  min = Math.min(min, histo.getBinContent(i+1, j+1, k+1));
2988 
2989  if (min>0) return; // if all points positive, no chance for autoscale
2990 
2991  var ileft = i2, iright = i1, jleft = j2, jright = j1, kleft = k2, kright = k1;
2992 
2993  for (i = i1; i < i2; ++i)
2994  for (j = j1; j < j2; ++j)
2995  for (k = k1; k < k2; ++k)
2996  if (histo.getBinContent(i+1, j+1, k+1) > min) {
2997  if (i < ileft) ileft = i;
2998  if (i >= iright) iright = i + 1;
2999  if (j < jleft) jleft = j;
3000  if (j >= jright) jright = j + 1;
3001  if (k < kleft) kleft = k;
3002  if (k >= kright) kright = k + 1;
3003  }
3004 
3005  var xmin, xmax, ymin, ymax, zmin, zmax, isany = false;
3006 
3007  if ((ileft === iright-1) && (ileft > i1+1) && (iright < i2-1)) { ileft--; iright++; }
3008  if ((jleft === jright-1) && (jleft > j1+1) && (jright < j2-1)) { jleft--; jright++; }
3009  if ((kleft === kright-1) && (kleft > k1+1) && (kright < k2-1)) { kleft--; kright++; }
3010 
3011  if ((ileft > i1 || iright < i2) && (ileft < iright - 1)) {
3012  xmin = histo.fXaxis.GetBinLowEdge(ileft+1);
3013  xmax = histo.fXaxis.GetBinLowEdge(iright+1);
3014  isany = true;
3015  }
3016 
3017  if ((jleft > j1 || jright < j2) && (jleft < jright - 1)) {
3018  ymin = histo.fYaxis.GetBinLowEdge(jleft+1);
3019  ymax = histo.fYaxis.GetBinLowEdge(jright+1);
3020  isany = true;
3021  }
3022 
3023  if ((kleft > k1 || kright < k2) && (kleft < kright - 1)) {
3024  zmin = histo.fZaxis.GetBinLowEdge(kleft+1);
3025  zmax = histo.fZaxis.GetBinLowEdge(kright+1);
3026  isany = true;
3027  }
3028 
3029  if (isany) this.frame_painter().Zoom(xmin, xmax, ymin, ymax, zmin, zmax);
3030  }
3031 
3032  TH3Painter.prototype.FillHistContextMenu = function(menu) {
3033 
3034  var sett = JSROOT.getDrawSettings("ROOT." + this.GetObject()._typename, 'nosame');
3035 
3036  menu.addDrawMenu("Draw with", sett.opts, function(arg) {
3037  if (arg==='inspect')
3038  return this.ShowInspector();
3039 
3040  this.DecodeOptions(arg);
3041 
3042  this.InteractiveRedraw(true, "drawopt");
3043  });
3044  }
3045 
3046  JSROOT.Painter.drawHistogram3D = function(divid, histo, opt) {
3047  // create painter and add it to canvas
3048  var painter = new JSROOT.TH3Painter(histo);
3049 
3050  painter.SetDivId(divid, 4);
3051 
3052  painter.DecodeOptions(opt);
3053 
3054  painter.CheckPadRange();
3055 
3056  painter.ScanContent();
3057 
3058  painter.Redraw();
3059 
3060  var stats = painter.CreateStat(); // only when required
3061  if (stats) JSROOT.draw(painter.divid, stats, "");
3062 
3063  painter.FillToolbar();
3064 
3065  return painter.DrawingReady();
3066  }
3067 
3068  // ===========================================================================================
3069 
3070  function TGraph2DPainter(graph) {
3071  JSROOT.TObjectPainter.call(this, graph);
3072  }
3073 
3074  TGraph2DPainter.prototype = Object.create(JSROOT.TObjectPainter.prototype);
3075 
3076  TGraph2DPainter.prototype.DecodeOptions = function(opt) {
3077  var d = new JSROOT.DrawOptions(opt);
3078 
3079  if (!this.options)
3080  this.options = {};
3081 
3082  var res = this.options;
3083 
3084  res.Color = d.check("COL");
3085  res.Line = d.check("LINE");
3086  res.Error = d.check("ERR") && this.MatchObjectType("TGraph2DErrors");
3087  res.Circles = d.check("P0");
3088  res.Markers = d.check("P");
3089 
3090  if (!res.Markers && !res.Error && !res.Circles && !res.Line) res.Markers = true;
3091  if (!res.Markers) res.Color = false;
3092 
3093  this.OptionsStore(opt);
3094  }
3095 
3096  TGraph2DPainter.prototype.CreateHistogram = function() {
3097  var gr = this.GetObject(),
3098  xmin = gr.fX[0], xmax = xmin,
3099  ymin = gr.fY[0], ymax = ymin,
3100  zmin = gr.fZ[0], zmax = zmin;
3101 
3102  for (var p = 0; p < gr.fNpoints;++p) {
3103 
3104  var x = gr.fX[p], y = gr.fY[p], z = gr.fZ[p],
3105  errx = this.options.Error ? gr.fEX[p] : 0,
3106  erry = this.options.Error ? gr.fEY[p] : 0,
3107  errz = this.options.Error ? gr.fEZ[p] : 0;
3108 
3109  xmin = Math.min(xmin, x-errx);
3110  xmax = Math.max(xmax, x+errx);
3111  ymin = Math.min(ymin, y-erry);
3112  ymax = Math.max(ymax, y+erry);
3113  zmin = Math.min(zmin, z-errz);
3114  zmax = Math.max(zmax, z+errz);
3115  }
3116 
3117  if (xmin >= xmax) xmax = xmin+1;
3118  if (ymin >= ymax) ymax = ymin+1;
3119  if (zmin >= zmax) zmax = zmin+1;
3120  var dx = (xmax-xmin)*0.02, dy = (ymax-ymin)*0.02, dz = (zmax-zmin)*0.02,
3121  uxmin = xmin - dx, uxmax = xmax + dx,
3122  uymin = ymin - dy, uymax = ymax + dy,
3123  uzmin = zmin - dz, uzmax = zmax + dz;
3124 
3125  if ((uxmin<0) && (xmin>=0)) uxmin = xmin*0.98;
3126  if ((uxmax>0) && (xmax<=0)) uxmax = 0;
3127 
3128  if ((uymin<0) && (ymin>=0)) uymin = ymin*0.98;
3129  if ((uymax>0) && (ymax<=0)) uymax = 0;
3130 
3131  if ((uzmin<0) && (zmin>=0)) uzmin = zmin*0.98;
3132  if ((uzmax>0) && (zmax<=0)) uzmax = 0;
3133 
3134  var graph = this.GetObject();
3135 
3136  if (graph.fMinimum != -1111) uzmin = graph.fMinimum;
3137  if (graph.fMaximum != -1111) uzmax = graph.fMaximum;
3138 
3139  var histo = JSROOT.CreateHistogram("TH2I", 10, 10);
3140  histo.fName = graph.fName + "_h";
3141  histo.fTitle = graph.fTitle;
3142  histo.fXaxis.fXmin = uxmin;
3143  histo.fXaxis.fXmax = uxmax;
3144  histo.fYaxis.fXmin = uymin;
3145  histo.fYaxis.fXmax = uymax;
3146  histo.fZaxis.fXmin = uzmin;
3147  histo.fZaxis.fXmax = uzmax;
3148  histo.fMinimum = uzmin;
3149  histo.fMaximum = uzmax;
3150  histo.fBits = histo.fBits | JSROOT.TH1StatusBits.kNoStats;
3151  return histo;
3152  }
3153 
3154  TGraph2DPainter.prototype.Graph2DTooltip = function(intersect) {
3155  if (isNaN(intersect.index)) {
3156  console.error('intersect.index not provided, check three.js version', THREE.REVISION, 'expected r97');
3157  return null;
3158  }
3159 
3160  var indx = Math.floor(intersect.index / this.nvertex);
3161  if ((indx<0) || (indx >= this.index.length)) return null;
3162 
3163  indx = this.index[indx];
3164 
3165  var p = this.painter,
3166  grx = p.grx(this.graph.fX[indx]),
3167  gry = p.gry(this.graph.fY[indx]),
3168  grz = p.grz(this.graph.fZ[indx]);
3169 
3170  if (this.check_next && indx+1<this.graph.fX.length) {
3171  function sqr(v) { return v*v; }
3172  var d = intersect.point,
3173  grx1 = p.grx(this.graph.fX[indx+1]),
3174  gry1 = p.gry(this.graph.fY[indx+1]),
3175  grz1 = p.grz(this.graph.fZ[indx+1]);
3176  if (sqr(d.x-grx1)+sqr(d.y-gry1)+sqr(d.z-grz1) < sqr(d.x-grx)+sqr(d.y-gry)+sqr(d.z-grz)) {
3177  grx = grx1; gry = gry1; grz = grz1; indx++;
3178  }
3179  }
3180 
3181  return {
3182  x1: grx - this.scale0,
3183  x2: grx + this.scale0,
3184  y1: gry - this.scale0,
3185  y2: gry + this.scale0,
3186  z1: grz - this.scale0,
3187  z2: grz + this.scale0,
3188  color: this.tip_color,
3189  lines: [ this.tip_name,
3190  "pnt: " + indx,
3191  "x: " + p.AxisAsText("x", this.graph.fX[indx]),
3192  "y: " + p.AxisAsText("y", this.graph.fY[indx]),
3193  "z: " + p.AxisAsText("z", this.graph.fZ[indx])
3194  ]
3195  }
3196  }
3197 
3199  TGraph2DPainter.prototype.PointsCallback = function(args, mesh) {
3200  // mesh.graph = args.graph;
3201  // mesh.index = args.index;
3202  // mesh.painter = args.painter;
3203  // mesh.scale0 = args.scale0;
3204  // mesh.tip_color = args.tip_color;
3205 
3206  JSROOT.extend(mesh, args);
3207 
3208  mesh.tip_name = this.GetTipName();
3209  mesh.tooltip = this.Graph2DTooltip;
3210 
3211  mesh.painter.toplevel.add(mesh);
3212 
3213  this.points_callback_cnt--;
3214 
3215  this.CheckCallbacks();
3216  }
3217 
3218  TGraph2DPainter.prototype.CheckCallbacks = function() {
3219  if (this.points_callback_cnt > 0) return;
3220 
3221  var fp = this.frame_painter();
3222  if (fp) fp.Render3D(100);
3223 
3224  if (!this._drawing_ready) {
3225  this.DrawingReady();
3226  this._drawing_ready = true;
3227  }
3228  }
3229 
3230  TGraph2DPainter.prototype.Redraw = function() {
3231 
3232  var main = this.main_painter(),
3233  fp = this.frame_painter(),
3234  graph = this.GetObject(),
3235  step = 1;
3236 
3237  if (!graph || !main || !fp || !fp.mode3d) return;
3238 
3239  function CountSelected(zmin, zmax) {
3240  var cnt = 0;
3241  for (var i=0; i < graph.fNpoints; ++i) {
3242  if ((graph.fX[i] < fp.scale_xmin) || (graph.fX[i] > fp.scale_xmax) ||
3243  (graph.fY[i] < fp.scale_ymin) || (graph.fY[i] > fp.scale_ymax) ||
3244  (graph.fZ[i] < zmin) || (graph.fZ[i] >= zmax)) continue;
3245 
3246  ++cnt;
3247  }
3248  return cnt;
3249  }
3250 
3251  // try to define scale-down factor
3252  if ((JSROOT.gStyle.OptimizeDraw > 0) && !fp.webgl) {
3253  var numselected = CountSelected(fp.scale_zmin, fp.scale_zmax),
3254  sizelimit = 50000;
3255 
3256  if (numselected > sizelimit) {
3257  step = Math.floor(numselected / sizelimit);
3258  if (step <= 2) step = 2;
3259  }
3260  }
3261 
3262  var markeratt = new JSROOT.TAttMarkerHandler(graph),
3263  palette = null,
3264  levels = [fp.scale_zmin, fp.scale_zmax],
3265  scale = fp.size_xy3d / 100 * markeratt.GetFullSize();
3266 
3267  if (this.options.Circles) scale = 0.06*fp.size_xy3d;
3268 
3269  if (fp.usesvg) scale*=0.3;
3270 
3271  if (this.options.Color) {
3272  levels = main.GetContour();
3273  palette = main.GetPalette();
3274  }
3275 
3276  // how many callbacks are expected
3277  this.points_callback_cnt = levels.length-1;
3278 
3279  for (var lvl=0;lvl<levels.length-1;++lvl) {
3280 
3281  var lvl_zmin = Math.max(levels[lvl], fp.scale_zmin),
3282  lvl_zmax = Math.min(levels[lvl+1], fp.scale_zmax);
3283 
3284  if (lvl_zmin >= lvl_zmax) {
3285  this.points_callback_cnt--;
3286  continue;
3287  }
3288 
3289  var size = Math.floor(CountSelected(lvl_zmin, lvl_zmax) / step),
3290  pnts = null, select = 0,
3291  index = new Int32Array(size), icnt = 0,
3292  err = null, line = null, ierr = 0, iline = 0;
3293 
3294  if (this.options.Markers || this.options.Circles)
3295  pnts = new JSROOT.Painter.PointsCreator(size, fp.webgl, scale/3);
3296 
3297  if (this.options.Error)
3298  err = new Float32Array(size*6*3);
3299 
3300  if (this.options.Line)
3301  line = new Float32Array((size-1)*6);
3302 
3303  for (var i=0; i < graph.fNpoints; ++i) {
3304  if ((graph.fX[i] < fp.scale_xmin) || (graph.fX[i] > fp.scale_xmax) ||
3305  (graph.fY[i] < fp.scale_ymin) || (graph.fY[i] > fp.scale_ymax) ||
3306  (graph.fZ[i] < lvl_zmin) || (graph.fZ[i] >= lvl_zmax)) continue;
3307 
3308  if (step > 1) {
3309  select = (select+1) % step;
3310  if (select!==0) continue;
3311  }
3312 
3313  index[icnt++] = i; // remember point index for tooltip
3314 
3315  var x = fp.grx(graph.fX[i]),
3316  y = fp.gry(graph.fY[i]),
3317  z = fp.grz(graph.fZ[i]);
3318 
3319  if (pnts) pnts.AddPoint(x,y,z);
3320 
3321  if (err) {
3322  err[ierr] = fp.grx(graph.fX[i] - graph.fEX[i]);
3323  err[ierr+1] = y;
3324  err[ierr+2] = z;
3325  err[ierr+3] = fp.grx(graph.fX[i] + graph.fEX[i]);
3326  err[ierr+4] = y;
3327  err[ierr+5] = z;
3328  ierr+=6;
3329  err[ierr] = x;
3330  err[ierr+1] = fp.gry(graph.fY[i] - graph.fEY[i]);
3331  err[ierr+2] = z;
3332  err[ierr+3] = x;
3333  err[ierr+4] = fp.gry(graph.fY[i] + graph.fEY[i]);
3334  err[ierr+5] = z;
3335  ierr+=6;
3336  err[ierr] = x;
3337  err[ierr+1] = y;
3338  err[ierr+2] = fp.grz(graph.fZ[i] - graph.fEZ[i]);
3339  err[ierr+3] = x;
3340  err[ierr+4] = y;
3341  err[ierr+5] = fp.grz(graph.fZ[i] + graph.fEZ[i]);;
3342  ierr+=6;
3343  }
3344 
3345  if (line) {
3346  if (iline>=6) {
3347  line[iline] = line[iline-3];
3348  line[iline+1] = line[iline-2];
3349  line[iline+2] = line[iline-1];
3350  iline+=3;
3351  }
3352  line[iline] = x;
3353  line[iline+1] = y;
3354  line[iline+2] = z;
3355  iline+=3;
3356  }
3357  }
3358 
3359  if (line && (iline>3) && (line.length == iline)) {
3360  var lcolor = this.get_color(this.GetObject().fLineColor),
3361  material = new THREE.LineBasicMaterial({ color: new THREE.Color(lcolor) });
3362  if (!JSROOT.browser.isIE) material.linewidth = this.GetObject().fLineWidth;
3363  var linemesh = JSROOT.Painter.createLineSegments(line, material);
3364  fp.toplevel.add(linemesh);
3365 
3366  linemesh.graph = graph;
3367  linemesh.index = index;
3368  linemesh.painter = fp;
3369  linemesh.scale0 = 0.7*scale;
3370  linemesh.tip_name = this.GetTipName();
3371  linemesh.tip_color = (graph.fMarkerColor === 3) ? 0xFF0000 : 0x00FF00;
3372  linemesh.nvertex = 2;
3373  linemesh.check_next = true;
3374 
3375  linemesh.tooltip = this.Graph2DTooltip;
3376  }
3377 
3378  if (err) {
3379  var lcolor = this.get_color(this.GetObject().fLineColor),
3380  material = new THREE.LineBasicMaterial({ color: new THREE.Color(lcolor) });
3381  if (!JSROOT.browser.isIE) material.linewidth = this.GetObject().fLineWidth;
3382  var errmesh = JSROOT.Painter.createLineSegments(err, material);
3383  fp.toplevel.add(errmesh);
3384 
3385  errmesh.graph = graph;
3386  errmesh.index = index;
3387  errmesh.painter = fp;
3388  errmesh.scale0 = 0.7*scale;
3389  errmesh.tip_name = this.GetTipName();
3390  errmesh.tip_color = (graph.fMarkerColor === 3) ? 0xFF0000 : 0x00FF00;
3391  errmesh.nvertex = 6;
3392 
3393  errmesh.tooltip = this.Graph2DTooltip;
3394  }
3395 
3396  if (!pnts) {
3397  this.points_callback_cnt--;
3398  } else {
3399 
3400  var fcolor = 'blue';
3401 
3402  if (!this.options.Circles)
3403  fcolor = palette ? palette.calcColor(lvl, levels.length) :
3404  this.get_color(graph.fMarkerColor);
3405 
3406  pnts.AssignCallback(this.PointsCallback.bind(this, {
3407  graph: graph,
3408  index: index,
3409  painter: fp,
3410  scale0: 0.3*scale,
3411  tip_color: (graph.fMarkerColor === 3) ? 0xFF0000 : 0x00FF00
3412  }));
3413 
3414  pnts.CreatePoints({ color: fcolor, style: this.options.Circles ? 4 : graph.fMarkerStyle });
3415  }
3416  }
3417 
3418  this.CheckCallbacks();
3419  }
3420 
3421  JSROOT.Painter.drawGraph2D = function(divid, gr, opt) {
3422 
3423  var painter = new JSROOT.TGraph2DPainter(gr);
3424 
3425  painter.SetDivId(divid, -1); // just to get access to existing elements
3426 
3427  painter.DecodeOptions(opt);
3428 
3429  if (painter.main_painter()) {
3430  painter.SetDivId(divid);
3431  painter.Redraw();
3432  return painter;
3433  }
3434 
3435  if (!gr.fHistogram)
3436  gr.fHistogram = painter.CreateHistogram();
3437 
3438  JSROOT.draw(divid, gr.fHistogram, "lego;axis", function(hpainter) {
3439  painter.ownhisto = true;
3440  painter.SetDivId(divid);
3441  painter.Redraw();
3442  });
3443 
3444  return painter;
3445  }
3446 
3447  // ===================================================================
3448 
3449  JSROOT.Painter.drawPolyMarker3D = function(divid, poly, opt) {
3450 
3451  var painter = new JSROOT.TObjectPainter(poly);
3452 
3453  painter.SetDivId(divid);
3454 
3455  painter.Redraw = function() {
3456 
3457  var fp = this.frame_painter();
3458 
3459  if (!fp || !fp.mode3d) {
3460  if (!this._did_redraw) this.DrawingReady();
3461  this._did_redraw = true;
3462  return;
3463  }
3464 
3465  var step = 1, sizelimit = 50000, numselect = 0;
3466 
3467  for (var i=0;i<poly.fP.length;i+=3) {
3468  if ((poly.fP[i] < fp.scale_xmin) || (poly.fP[i] > fp.scale_xmax) ||
3469  (poly.fP[i+1] < fp.scale_ymin) || (poly.fP[i+1] > fp.scale_ymax) ||
3470  (poly.fP[i+2] < fp.scale_zmin) || (poly.fP[i+2] > fp.scale_zmax)) continue;
3471  ++numselect;
3472  }
3473 
3474  if ((JSROOT.gStyle.OptimizeDraw > 0) && (numselect > sizelimit)) {
3475  step = Math.floor(numselect/sizelimit);
3476  if (step <= 2) step = 2;
3477  }
3478 
3479  var size = Math.floor(numselect/step),
3480  pnts = new JSROOT.Painter.PointsCreator(size, fp.webgl, fp.size_xy3d/100),
3481  index = new Int32Array(size),
3482  select = 0, icnt = 0;
3483 
3484  for (var i=0; i<poly.fP.length; i+=3) {
3485 
3486  if ((poly.fP[i] < fp.scale_xmin) || (poly.fP[i] > fp.scale_xmax) ||
3487  (poly.fP[i+1] < fp.scale_ymin) || (poly.fP[i+1] > fp.scale_ymax) ||
3488  (poly.fP[i+2] < fp.scale_zmin) || (poly.fP[i+2] > fp.scale_zmax)) continue;
3489 
3490  if (step > 1) {
3491  select = (select+1) % step;
3492  if (select!==0) continue;
3493  }
3494 
3495  index[icnt++] = i;
3496 
3497  pnts.AddPoint(fp.grx(poly.fP[i]), fp.gry(poly.fP[i+1]), fp.grz(poly.fP[i+2]));
3498  }
3499 
3500  pnts.AssignCallback(function(mesh) {
3501  fp.toplevel.add(mesh);
3502 
3503  mesh.tip_color = (poly.fMarkerColor === 3) ? 0xFF0000 : 0x00FF00;
3504  mesh.tip_name = poly.fName || "Poly3D";
3505  mesh.poly = poly;
3506  mesh.painter = fp;
3507  mesh.scale0 = 0.7*pnts.scale;
3508  mesh.index = index;
3509 
3510  mesh.tooltip = function(intersect) {
3511  if (isNaN(intersect.index)) {
3512  console.error('intersect.index not provided, check three.js version', THREE.REVISION, 'expected r97');
3513  return null;
3514  }
3515  var indx = Math.floor(intersect.index / this.nvertex);
3516  if ((indx<0) || (indx >= this.index.length)) return null;
3517 
3518  indx = this.index[indx];
3519 
3520  var p = this.painter,
3521  grx = p.grx(this.poly.fP[indx]),
3522  gry = p.gry(this.poly.fP[indx+1]),
3523  grz = p.grz(this.poly.fP[indx+2]);
3524 
3525  return {
3526  x1: grx - this.scale0,
3527  x2: grx + this.scale0,
3528  y1: gry - this.scale0,
3529  y2: gry + this.scale0,
3530  z1: grz - this.scale0,
3531  z2: grz + this.scale0,
3532  color: this.tip_color,
3533  lines: [ this.tip_name,
3534  "pnt: " + indx/3,
3535  "x: " + p.AxisAsText("x", this.poly.fP[indx]),
3536  "y: " + p.AxisAsText("y", this.poly.fP[indx+1]),
3537  "z: " + p.AxisAsText("z", this.poly.fP[indx+2])
3538  ]
3539  }
3540 
3541  }
3542 
3543  fp.Render3D(100); // set large timeout to be able draw other points
3544 
3545  if (!painter._did_redraw) painter.DrawingReady();
3546  painter._did_redraw = true;
3547 
3548  });
3549 
3550  pnts.CreatePoints({ color: this.get_color(poly.fMarkerColor),
3551  style: poly.fMarkerStyle });
3552  }
3553 
3554  painter.Redraw();
3555 
3556  return painter;
3557  }
3558 
3559  JSROOT.TH3Painter = TH3Painter;
3560  JSROOT.TGraph2DPainter = TGraph2DPainter;
3561 
3562  return JSROOT;
3563 }));