import { settings, gStyle, clTMultiGraph, kNoZoom } from '../core.mjs';
import { getMaterialArgs, THREE } from '../base/base3d.mjs';
import { assignFrame3DMethods, drawBinsLego, drawBinsError3D, drawBinsContour3D, drawBinsSurf3D } from './hist3d.mjs';
import { TAxisPainter } from '../gpad/TAxisPainter.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(),
pmain = painter.getFramePainter(),
axis_zmin = pmain.z_handle.getScaleMin(),
axis_zmax = pmain.z_handle.getScaleMax(),
len = histo.fBins.arr.length,
z0 = pmain.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 > pmain.scale_xmax) || (bin.fXmax < pmain.scale_xmin) ||
(bin.fYmin > pmain.scale_ymax) || (bin.fYmax < pmain.scale_ymin)) continue;
z1 = pmain.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 = pmain.size_x3d*pmain.size_z3d;
const dist2limit = (ntry > 0) ? 0 : dist2/1e6;
pnts = []; faces = null;
for (let vert = 0; vert < npnts; ++vert) {
currx = pmain.grx(x[vert]);
curry = pmain.gry(y[vert]);
if (vert > 0)
dist2 = (currx-lastx)*(currx-lastx) + (curry-lasty)*(curry-lasty);
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[n], pnt2 = pnts[n > 0 ? n - 1 : pnts.length - 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._color_palette?.getColor(colindx), { vertexColors: false, side: THREE.DoubleSide })),
mesh = new THREE.Mesh(geometry, material);
pmain.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, fp = p.getFramePainter(),
tbin = p.getObject().fBins.arr[this.bins_index],
tip = {
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)
};
return tip;
};
}
}
/** @summary Draw 2-D histogram in 3D
* @private */
class TH2Painter extends TH2Painter2D {
/** @summary draw TH2 object in 3D mode */
async draw3D(reason) {
this.mode3d = true;
const main = this.getFramePainter(), // who makes axis drawing
is_main = this.isMainPainter(), // is main histogram
histo = this.getHisto();
let pr = Promise.resolve(true), full_draw = true;
if (reason === 'resize') {
const res = is_main ? main.resize3D() : false;
if (res !== 1) {
full_draw = false;
if (res)
main.render3D();
}
}
if (full_draw) {
const pad = this.getPadPainter().getRootPad(true),
logz = pad?.fLogv ?? pad?.fLogz;
let zmult = 1;
if (this.options.ohmin && this.options.ohmax) {
this.zmin = this.options.hmin;
this.zmax = this.options.hmax;
} else if (this.options.minimum !== kNoZoom && this.options.maximum !== kNoZoom) {
this.zmin = this.options.minimum;
this.zmax = this.options.maximum;
} else if (this.draw_content || (this.gmaxbin !== 0)) {
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);
if (is_main) {
assignFrame3DMethods(main);
pr = main.create3DScene(this.options.Render3D, this.options.x3dscale, this.options.y3dscale, this.options.Ortho).then(() => {
main.setAxesRanges(histo.fXaxis, this.xmin, this.xmax, histo.fYaxis, this.ymin, this.ymax, histo.fZaxis, this.zmin, this.zmax, this);
main.set3DOptions(this.options);
main.drawXYZ(main.toplevel, TAxisPainter, {
ndim: 2, hist_painter: this, zmult, zoom: settings.Zooming,
draw: this.options.Axis !== -1, drawany: this.options.isCartesian(),
reverse_x: this.options.RevX, reverse_y: this.options.RevY
});
});
}
if (main.mode3d) {
pr = pr.then(() => {
if (this.draw_content) {
if (this.isTH2Poly())
drawTH2PolyLego(this);
else if (this.options.Contour)
drawBinsContour3D(this, true);
else if (this.options.Surf)
drawBinsSurf3D(this);
else if (this.options.Error)
drawBinsError3D(this);
else
drawBinsLego(this);
} else if (this.options.Axis && this.options.Zscale) {
this.getContourLevels(true);
this.getHistPalette();
}
main.render3D();
this.updateStatWebCanvas();
main.addKeysHandler();
});
}
}
// (re)draw palette by resize while canvas may change dimension
if (is_main) {
pr = pr.then(() => this.drawColorPalette(this.options.Zscale && ((this.options.Lego === 12) || (this.options.Lego === 14) ||
(this.options.Surf === 11) || (this.options.Surf === 12))));
}
return pr.then(() => this.updateFunctions())
.then(() => this.updateHistTitle())
.then(() => this);
}
/** @summary draw TH2 object */
static async draw(dom, histo, opt) {
return THistPainter._drawHist(new TH2Painter(dom, histo), opt);
}
} // class TH2Painter
export { TH2Painter };