UNPKG

jsroot

Version:
335 lines (271 loc) 10.3 kB
import { gStyle, clTMultiGraph, kNoZoom } from '../core.mjs'; import { getMaterialArgs, THREE } from '../base/base3d.mjs'; import { crete3DFrame, drawBinsLego, drawBinsError3D, drawBinsContour3D, drawBinsSurf3D } from './hist3d.mjs'; import { TAxisPainter } from '../gpad/TAxisPainter.mjs'; import { TFramePainter } from '../gpad/TFramePainter.mjs'; import { THistPainter } from '../hist2d/THistPainter.mjs'; import { TH2Painter as TH2Painter2D } from '../hist2d/TH2Painter.mjs'; /** @summary Draw TH2Poly histogram as lego * @private */ function drawTH2PolyLego(painter) { const histo = painter.getHisto(), fp = painter.getFramePainter(), axis_zmin = fp.z_handle.getScaleMin(), axis_zmax = fp.z_handle.getScaleMax(), len = histo.fBins.arr.length, z0 = fp.grz(axis_zmin); let colindx, bin, i, z1; // use global coordinates painter.maxbin = painter.gmaxbin; painter.minbin = painter.gminbin; painter.minposbin = painter.gminposbin; const cntr = painter.getContour(true), palette = painter.getHistPalette(); for (i = 0; i < len; ++i) { bin = histo.fBins.arr[i]; if (bin.fContent < axis_zmin) continue; colindx = cntr.getPaletteIndex(palette, bin.fContent); if (colindx === null) continue; // check if bin outside visible range if ((bin.fXmin > fp.scale_xmax) || (bin.fXmax < fp.scale_xmin) || (bin.fYmin > fp.scale_ymax) || (bin.fYmax < fp.scale_ymin)) continue; z1 = fp.grz((bin.fContent > axis_zmax) ? axis_zmax : bin.fContent); const all_pnts = [], all_faces = []; let ngraphs = 1, gr = bin.fPoly, nfaces = 0; if (gr._typename === clTMultiGraph) { ngraphs = bin.fPoly.fGraphs.arr.length; gr = null; } for (let ngr = 0; ngr < ngraphs; ++ngr) { if (!gr || (ngr > 0)) gr = bin.fPoly.fGraphs.arr[ngr]; const x = gr.fX, y = gr.fY; let npnts = gr.fNpoints; while ((npnts > 2) && (x[0] === x[npnts - 1]) && (y[0] === y[npnts - 1])) --npnts; let pnts, faces; for (let ntry = 0; ntry < 2; ++ntry) { // run two loops - on the first try to compress data, on second - run as is (removing duplication) let lastx, lasty, currx, curry, dist2 = fp.size_x3d * fp.size_z3d; const dist2limit = (ntry > 0) ? 0 : dist2 / 1e6; pnts = []; faces = null; for (let vert = 0; vert < npnts; ++vert) { currx = fp.grx(x[vert]); curry = fp.gry(y[vert]); if (vert > 0) dist2 = (currx - lastx) ** 2 + (curry - lasty) ** 2; if (dist2 > dist2limit) { pnts.push(new THREE.Vector2(currx, curry)); lastx = currx; lasty = curry; } } try { if (pnts.length > 2) faces = THREE.ShapeUtils.triangulateShape(pnts, []); } catch { faces = null; } if (faces && (faces.length > pnts.length - 3)) break; } if (faces?.length && pnts) { all_pnts.push(pnts); all_faces.push(faces); nfaces += faces.length * 2; if (z1 > z0) nfaces += pnts.length * 2; } } const pos = new Float32Array(nfaces * 9); let indx = 0; for (let ngr = 0; ngr < all_pnts.length; ++ngr) { const pnts = all_pnts[ngr], faces = all_faces[ngr]; for (let layer = 0; layer < 2; ++layer) { for (let n = 0; n < faces.length; ++n) { const face = faces[n], pnt1 = pnts[face[0]], pnt2 = pnts[face[layer === 0 ? 2 : 1]], pnt3 = pnts[face[layer === 0 ? 1 : 2]]; pos[indx] = pnt1.x; pos[indx + 1] = pnt1.y; pos[indx + 2] = layer ? z1 : z0; indx += 3; pos[indx] = pnt2.x; pos[indx + 1] = pnt2.y; pos[indx + 2] = layer ? z1 : z0; indx += 3; pos[indx] = pnt3.x; pos[indx + 1] = pnt3.y; pos[indx + 2] = layer ? z1 : z0; indx += 3; } } if (z1 > z0) { for (let n = 0; n < pnts.length; ++n) { const pnt1 = pnts.at(n), pnt2 = pnts.at(n > 0 ? n - 1 : - 1); pos[indx] = pnt1.x; pos[indx + 1] = pnt1.y; pos[indx + 2] = z0; indx += 3; pos[indx] = pnt2.x; pos[indx + 1] = pnt2.y; pos[indx + 2] = z0; indx += 3; pos[indx] = pnt2.x; pos[indx + 1] = pnt2.y; pos[indx + 2] = z1; indx += 3; pos[indx] = pnt1.x; pos[indx + 1] = pnt1.y; pos[indx + 2] = z0; indx += 3; pos[indx] = pnt2.x; pos[indx + 1] = pnt2.y; pos[indx + 2] = z1; indx += 3; pos[indx] = pnt1.x; pos[indx + 1] = pnt1.y; pos[indx + 2] = z1; indx += 3; } } } const geometry = new THREE.BufferGeometry(); geometry.setAttribute('position', new THREE.BufferAttribute(pos, 3)); geometry.computeVertexNormals(); const material = new THREE.MeshBasicMaterial(getMaterialArgs(painter.getHistPalette()?.getColor(colindx), { vertexColors: false, side: THREE.DoubleSide })), mesh = new THREE.Mesh(geometry, material); fp.add3DMesh(mesh); mesh.painter = painter; mesh.bins_index = i; mesh.draw_z0 = z0; mesh.draw_z1 = z1; mesh.tip_color = 0x00FF00; mesh.tooltip = function(/* intersects */) { const p = this.painter, tbin = p.getObject().fBins.arr[this.bins_index]; return { use_itself: true, // indicate that use mesh itself for highlighting x1: fp.grx(tbin.fXmin), x2: fp.grx(tbin.fXmax), y1: fp.gry(tbin.fYmin), y2: fp.gry(tbin.fYmax), z1: this.draw_z0, z2: this.draw_z1, bin: this.bins_index, value: bin.fContent, color: this.tip_color, lines: p.getPolyBinTooltips(this.bins_index) }; }; } } /** @summary Draw 2-D histogram in 3D * @private */ class TH2Painter extends TH2Painter2D { /** @summary Check range for 3D * @private */ checkRangeFor3D(o) { const pad = this.getPadPainter()?.getRootPad(true), logz = pad?.fLogv ?? pad?.fLogz; let zmult = 1; if (o.ohmin && o.ohmax) { this.zmin = o.hmin; this.zmax = o.hmax; } else if (o.minimum !== kNoZoom && o.maximum !== kNoZoom) { this.zmin = o.minimum; this.zmax = o.maximum; } else if (this.draw_content || this.gmaxbin) { this.zmin = logz ? this.gminposbin * 0.3 : this.gminbin; this.zmax = this.gmaxbin; zmult = 1 + 2 * gStyle.fHistTopMargin; } if (logz && (this.zmin <= 0)) this.zmin = this.zmax * 1e-5; this.createHistDrawAttributes(true); return zmult; } /** @summary Create 3D object for histogram bins * @private */ draw3DBins(o) { if (this.isTH2Poly()) drawTH2PolyLego(this); else if (o.Contour) drawBinsContour3D(this, true); else if (o.Surf) drawBinsSurf3D(this); else if (o.Error) drawBinsError3D(this); else drawBinsLego(this); } /** @summary draw TH2 object in 3D mode */ async draw3D(reason) { this.mode3d = true; const fp = this.getFramePainter(), // who makes axis drawing is_main = this.isMainPainter(), // is main histogram o = this.getOptions(); let pr = Promise.resolve(true), full_draw = true; if (reason === 'resize') { const res = is_main ? fp.resize3D() : false; if (res !== 1) { full_draw = false; if (res) fp.render3D(); } } if (full_draw) { o.zmult = this.checkRangeFor3D(o); if (is_main) pr = crete3DFrame(this, TAxisPainter, o.Render3D); if (fp.mode3d) { pr = pr.then(() => { if (this.draw_content) this.draw3DBins(o); else if (o.Axis && o.Zscale) { this.getContourLevels(true); this.getHistPalette(); } fp.render3D(); this.updateStatWebCanvas(); fp.addKeysHandler(); }); } } // (re)draw palette by resize while canvas may change dimension if (is_main) { pr = pr.then(() => this.drawColorPalette(o.Zscale && ((o.Lego === 12) || (o.Lego === 14) || (o.Surf === 11) || (o.Surf === 12)))); } return pr.then(() => this.updateFunctions()) .then(() => this.updateHistTitle()) .then(() => this); } /** @summary Build three.js object for the histogram */ static async build3d(histo, opt, get_painter) { const painter = new TH2Painter(null, histo); painter.decodeOptions(opt); const o = painter.getOptions(); if (painter.isTH2Poly()) o.Lego = 12; painter.scanContent(); o.zmult = painter.checkRangeFor3D(o); const fp = new TFramePainter(null, null); // return dummy frame painter as result painter.getFramePainter = () => fp; return crete3DFrame(painter, TAxisPainter).then(() => { if (painter.draw_content) painter.draw3DBins(o); return get_painter ? painter : fp.create3DScene(-1, true); }); } /** @summary draw TH2 object */ static async draw(dom, histo, opt) { return THistPainter._drawHist(new TH2Painter(dom, histo), opt); } } // class TH2Painter export { TH2Painter };