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 };