diff --git a/build.gradle.kts b/build.gradle.kts index 74908dc9..afa849c6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -106,7 +106,7 @@ subprojects { metaInfo = true sourceMap = true sourceMapEmbedSources = "always" - moduleKind = "umd" + moduleKind = "commonjs" } } } diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/jsroot/JSRoot.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/jsroot/JSRoot.kt index 7b743148..9a355a6a 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/jsroot/JSRoot.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/jsroot/JSRoot.kt @@ -1,4 +1,4 @@ -@file:JsModule("JSRootCore.js") +@file:JsModule("JSRootUtils") @file:JsNonModule package hep.dataforge.vis.spatial.jsroot diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/jsroot/JSRootDemoApp.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/jsroot/JSRootDemoApp.kt index 5a50fef9..25254c35 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/jsroot/JSRootDemoApp.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/jsroot/JSRootDemoApp.kt @@ -19,8 +19,6 @@ class JSRootDemoApp : ApplicationBase() { override val stateKeys: List = emptyList() override fun start(state: Map) { -// require("JSRootGeoBase.js") -// require("JSRootCore.js") //TODO remove after DI fix diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/jsroot/JSRootGEO.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/jsroot/JSRootGEO.kt index c6391993..9d3fc17d 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/jsroot/JSRootGEO.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/jsroot/JSRootGEO.kt @@ -1,4 +1,4 @@ -@file:JsModule("JSRootGeoBase.js") +@file:JsModule("JSRootGeoBase") @file:JsNonModule package hep.dataforge.vis.spatial.jsroot diff --git a/dataforge-vis-spatial-js/src/main/resources/JSRootCore.js b/dataforge-vis-spatial-js/src/main/resources/JSRootCore.js deleted file mode 100644 index 9a33ae41..00000000 --- a/dataforge-vis-spatial-js/src/main/resources/JSRootCore.js +++ /dev/null @@ -1,1936 +0,0 @@ -(function (factory) { - if (typeof define === "function" && define.amd) { - define([], factory); - } else if (typeof exports === 'object' && typeof module !== 'undefined') { - factory(exports); - } else { - if (typeof JSROOT != 'undefined') - throw new Error("JSROOT is already defined", "JSRootCore.js"); - - JSROOT = {}; - - factory(JSROOT); - } -}(function (JSROOT) { - "use strict"; - - if (!JSROOT) JSROOT = {}; - - JSROOT.version = "dev 25/03/2019"; - - JSROOT.source_dir = ""; - JSROOT.source_min = false; - JSROOT.source_fullpath = ""; // full name of source script - JSROOT.bower_dir = null; // when specified, use standard libs from bower location - JSROOT.nocache = false; - JSROOT.sources = ['core']; // indicates which major sources were loaded - - JSROOT.id_counter = 0; - if (JSROOT.BatchMode === undefined) - JSROOT.BatchMode = false; // when true, disables all kind of interactive features - - //openuicfg // DO NOT DELETE, used to configure openui5 usage like JSROOT.openui5src = "nojsroot"; - - // JSROOT.use_full_libs = true; - - JSROOT.touches = false; - JSROOT.browser = {isOpera: false, isFirefox: true, isSafari: false, isChrome: false, isIE: false, isWin: false}; - - if ((typeof document !== "undefined") && (typeof window !== "undefined")) { - let scripts = document.getElementsByTagName('script'); - for (var n = 0; n < scripts.length; ++n) { - if (!scripts[n].src || (typeof scripts[n].src !== 'string')) continue; - - var pos = scripts[n].src.indexOf("scripts/JSRootCore."); - if (pos < 0) continue; - - JSROOT.source_dir = scripts[n].src.substr(0, pos); - JSROOT.source_min = scripts[n].src.indexOf("scripts/JSRootCore.min.js") >= 0; - JSROOT.source_fullpath = scripts[n].src; - - if ((console !== undefined) && (typeof console.log == 'function')) - console.log("Set JSROOT.source_dir to " + JSROOT.source_dir + ", " + JSROOT.version); - break; - } - - JSROOT.touches = ('ontouchend' in document); // identify if touch events are supported - JSROOT.browser.isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; - JSROOT.browser.isFirefox = typeof InstallTrigger !== 'undefined'; - JSROOT.browser.isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; - JSROOT.browser.isChrome = !!window.chrome && !JSROOT.browser.isOpera; - JSROOT.browser.isIE = !!document.documentMode; - JSROOT.browser.isWin = navigator.platform.indexOf('Win') >= 0; - JSROOT.browser.isChromeHeadless = navigator.userAgent.indexOf('HeadlessChrome') >= 0; - } - - JSROOT.browser.isWebKit = JSROOT.browser.isChrome || JSROOT.browser.isSafari || JSROOT.browser.isOpera; - - // default draw styles, can be changed after loading of JSRootCore.js - // this style also can be changed providing style=itemname in the URL - JSROOT.gStyle = { - Tooltip: 1, // 0 - off, 1 - on - TooltipAnimation: 500, // time in msec for appearance of tooltips, 0 - no animation - ContextMenu: true, - Zooming: true, // global zooming flag, enable/disable any kind of interactive zooming - ZoomMouse: true, // Zooming with the mouse events - ZoomWheel: true, // Zooming with mouse wheel - ZoomTouch: true, // Zooming with the touch devices - MoveResize: true, // enable move and resize of elements like statbox, title, pave, colz - DragAndDrop: true, // enables drag and drop functionality - ToolBar: 'popup', // show additional tool buttons on the canvas, false - disabled, true - enabled, 'popup' - only toggle button - ToolBarSide: 'left', // 'left' left-bottom corner on canvas, 'right' - right-bottom corner on canvas, opposite on sub-pads - ToolBarVert: false, // display tool bar vertical (default false) - CanEnlarge: true, // if drawing inside particular div can be enlarged on full window - CanAdjustFrame: false, // if frame position can be adjusted to let show axis or colz labels - ApproxTextSize: false, // calculation of text size consumes time and can be skipped to improve performance (but with side effects on text adjustments) - OptimizeDraw: 1, // drawing optimization: 0 - disabled, 1 - only for large (>5000 1d bins, >50 2d bins) histograms, 2 - always - AutoStat: true, - FrameNDC: {fX1NDC: 0.07, fY1NDC: 0.12, fX2NDC: 0.95, fY2NDC: 0.88}, - Palette: 57, - Latex: 2, // 0 - never, 1 - only latex symbols, 2 - normal TLatex processing (default), 3 - use MathJax for complex case, 4 - use MathJax always - // MathJax : 0, // depricated, will be supported till JSROOT 6.0, use Latex variable 0 - never, 1 - only for complex cases, 2 - always - ProgressBox: true, // show progress box - Embed3DinSVG: 2, // 0 - no embed, only 3D plot, 1 - overlay over SVG (IE/WebKit), 2 - embed into SVG (only Firefox) - ImageSVG: !JSROOT.nodejs, // when producing SVG images, use elements to insert 3D drawings from three.js, - // To enable on nodejs, one should call "npm install canvas" - NoWebGL: false, // if true, WebGL will be disabled - GeoGradPerSegm: 6, // amount of grads per segment in TGeo spherical shapes like tube - GeoCompressComp: true, // if one should compress faces after creation of composite shape, - IgnoreUrlOptions: false, // if true, ignore all kind of URL options in the browser URL - HierarchyLimit: 250, // how many items shown on one level of hierarchy - SmallPad: {width: 150, height: 100}, // size of pad, where many features will be deactivated like text draw or zooming - - // XValuesFormat : "6.4g", // custom format for all X values - // YValuesFormat : "6.4g", // custom format for all Y values - // ZValuesFormat : "6.4g", // custom format for all Z values - - // these are TStyle attributes, which can be changed via URL 'style' parameter or delivered by TWebCanvas - - fOptLogx: 0, - fOptLogy: 0, - fOptLogz: 0, - fOptDate: 0, - fOptFile: 0, - fOptTitle: 1, - fPadBottomMargin: 0.1, - fPadTopMargin: 0.1, - fPadLeftMargin: 0.1, - fPadRightMargin: 0.1, - fPadGridX: false, - fPadGridY: false, - fPadTickX: 0, - fPadTickY: 0, - fStatColor: 0, - fStatTextColor: 1, - fStatBorderSize: 1, - fStatFont: 42, - fStatFontSize: 0, - fStatStyle: 1001, - fStatFormat: "6.4g", - fStatX: 0.98, - fStatY: 0.935, - fStatW: 0.2, - fStatH: 0.16, - fTitleAlign: 23, - fTitleColor: 0, - fTitleTextColor: 1, - fTitleBorderSize: 0, - fTitleFont: 42, - fTitleFontSize: 0.05, - fTitleStyle: 0, - fTitleX: 0.5, - fTitleY: 0.995, - fTitleW: 0, - fTitleH: 0, - fFitFormat: "5.4g", - fOptStat: 1111, - fOptFit: 0, - fNumberContours: 20, - fGridColor: 0, - fGridStyle: 3, - fGridWidth: 1, - fFrameFillColor: 0, - fFrameFillStyle: 1001, - fFrameLineColor: 1, - fFrameLineWidth: 1, - fFrameLineStyle: 1, - fFrameBorderSize: 1, - fFrameBorderMode: 0, - fEndErrorSize: 2, // size in pixels of end error for E1 draw options - fErrorX: 0.5, // X size of the error marks for the histogram drawings - fHistMinimumZero: false, // when true, BAR and LEGO drawing using base = 0 - fPaintTextFormat: "g", - fTimeOffset: 788918400 // UTC time at 01/01/95 - }; - - /** Generate mask for given bit - * - * @param {number} n bit number - * @returns {Number} produced make - * @private */ - JSROOT.BIT = function (n) { - return 1 << (n); - }; - - /** TH1 status bits - * @private */ - JSROOT.TH1StatusBits = { - kNoStats: JSROOT.BIT(9), // don't draw stats box - kUserContour: JSROOT.BIT(10), // user specified contour levels - kCanRebin: JSROOT.BIT(11), // can rebin axis - kLogX: JSROOT.BIT(15), // X-axis in log scale - kIsZoomed: JSROOT.BIT(16), // bit set when zooming on Y axis - kNoTitle: JSROOT.BIT(17), // don't draw the histogram title - kIsAverage: JSROOT.BIT(18) // Bin contents are average (used by Add) - }; - - /** Wrapper for console.log, let redirect output to specified div element - * @private */ - JSROOT.console = function (value, divid) { - if ((typeof divid == 'string') && document.getElementById(divid)) - document.getElementById(divid).innerHTML = value; - else if ((typeof console != 'undefined') && (typeof console.log == 'function')) - console.log(value); - }; - - /** @summary Wrapper for alert, throws Error in Node.js - * @private */ - JSROOT.alert = function (msg) { - if (this.nodeis) throw new Error(msg); - if (typeof alert === 'function') alert(msg); - else JSROOT.console('ALERT: ' + msg); - }; - - /** - * @summary Seed simple random generator - * - * @private - * @param {number} i seed value - */ - JSROOT.seed = function (i) { - i = Math.abs(i); - if (i > 1e8) i = Math.abs(1e8 * Math.sin(i)); else if (i < 1) i *= 1e8; - this.m_w = Math.round(i); - this.m_z = 987654321; - }; - - /** - * @summary Simple random generator - * - * @desc Works like Math.random(), but with configurable seed - see {@link JSROOT.seed} - * @private - * @returns {number} random value between 0 (inclusive) and 1.0 (exclusive) - */ - JSROOT.random = function () { - if (this.m_z === undefined) return Math.random(); - this.m_z = (36969 * (this.m_z & 65535) + (this.m_z >> 16)) & 0xffffffff; - this.m_w = (18000 * (this.m_w & 65535) + (this.m_w >> 16)) & 0xffffffff; - var result = ((this.m_z << 16) + this.m_w) & 0xffffffff; - result /= 4294967296; - return result + 0.5; - }; - - /** @summary Should be used to reintroduce objects references, produced by TBufferJSON. - * - * @desc Replace all references inside object, object should not be null - * Idea of the code taken from JSON-R code, found on - * https://github.com/graniteds/jsonr - * Only unref part was used, arrays are not accounted as objects - * @param {object} obj object where references will be replaced - * @returns {object} same object with replaced references - * @private */ - JSROOT.JSONR_unref = function (obj) { - - var map = [], newfmt = undefined; - - function unref_value(value) { - if ((value === null) || (value === undefined)) return; - - if (typeof value === 'string') { - if (newfmt || (value.length < 6) || (value.indexOf("$ref:") !== 0)) return; - var ref = parseInt(value.substr(5)); - if (isNaN(ref) || (ref < 0) || (ref >= map.length)) return; - newfmt = false; - return map[ref]; - } - - if (typeof value !== 'object') return; - - var i, k, res, proto = Object.prototype.toString.apply(value); - - // scan array - it can contain other objects - if ((proto.indexOf('[object') === 0) && (proto.indexOf('Array]') > 0)) { - for (i = 0; i < value.length; ++i) { - res = unref_value(value[i]); - if (res !== undefined) value[i] = res; - } - return; - } - - var ks = Object.keys(value), len = ks.length; - - if ((newfmt !== false) && (len === 1) && (ks[0] === '$ref')) { - var ref = parseInt(value['$ref']); - if (isNaN(ref) || (ref < 0) || (ref >= map.length)) return; - newfmt = true; - return map[ref]; - } - - if ((newfmt !== false) && (len > 1) && (ks[0] === '$arr') && (ks[1] === 'len')) { - // this is ROOT-coded array - var arr = null, dflt = (value.$arr === "Bool") ? false : 0; - switch (value.$arr) { - case "Int8" : - arr = new Int8Array(value.len); - break; - case "Uint8" : - arr = new Uint8Array(value.len); - break; - case "Int16" : - arr = new Int16Array(value.len); - break; - case "Uint16" : - arr = new Uint16Array(value.len); - break; - case "Int32" : - arr = new Int32Array(value.len); - break; - case "Uint32" : - arr = new Uint32Array(value.len); - break; - case "Float32" : - arr = new Float32Array(value.len); - break; - case "Int64" : - case "Uint64" : - case "Float64" : - arr = new Float64Array(value.len); - break; - default : - arr = new Array(value.len); - break; - } - for (let k = 0; k < value.len; ++k) arr[k] = dflt; - - var nkey = 2, p = 0; - while (nkey < len) { - if (ks[nkey][0] === "p") p = value[ks[nkey++]]; // position - if (ks[nkey][0] !== 'v') throw new Error('Unexpected member ' + ks[nkey] + ' in array decoding'); - var v = value[ks[nkey++]]; // value - if (typeof v === 'object') { - for (let k = 0; k < v.length; ++k) arr[p++] = v[k]; - } else { - arr[p++] = v; - if ((nkey < len) && (ks[nkey][0] === 'n')) { - var cnt = value[ks[nkey++]]; // counter - while (--cnt) arr[p++] = v; - } - } - } - - return arr; - } - - if ((newfmt !== false) && (len === 3) && (ks[0] === '$pair') && (ks[1] === 'first') && (ks[2] === 'second')) { - newfmt = true; - var f1 = unref_value(value.first), - s1 = unref_value(value.second); - if (f1 !== undefined) value.first = f1; - if (s1 !== undefined) value.second = s1; - value._typename = value['$pair']; - delete value['$pair']; - return; // pair object is not counted in the objects map - } - - // debug code, can be commented out later - if (map.indexOf(value) >= 0) { - JSROOT.console('should never happen - object already in the map'); - return; - } - - // add object to object map - map.push(value); - - // add methods to all objects, where _typename is specified - if ('_typename' in value) JSROOT.addMethods(value); - - for (k = 0; k < len; ++k) { - i = ks[k]; - res = unref_value(value[i]); - if (res !== undefined) value[i] = res; - } - } - - unref_value(obj); - - return obj; - }; - - JSROOT.debug = 0; - - /** @summary Just copies (not clone) all fields from source to the target object - * @desc This is simple replacement of jQuery.extend method - * @private */ - JSROOT.extend = function (tgt, src) { - if ((src === null) || (typeof src !== 'object')) return tgt; - if ((tgt === null) || (typeof tgt !== 'object')) tgt = {}; - - for (var k in src) - tgt[k] = src[k]; - - return tgt; - }; - - /** @summary Make deep clone of the object, including all sub-objects - * @private */ - JSROOT.clone = function (src, map, nofunc) { - if (src === null) return null; - - if (!map) { - map = {obj: [], clones: [], nofunc: nofunc}; - } else { - var i = map.obj.indexOf(src); - if (i >= 0) return map.clones[i]; - } - - var proto = Object.prototype.toString.apply(src); - - // process normal array - if (proto === '[object Array]') { - var tgt = []; - map.obj.push(src); - map.clones.push(tgt); - for (var i = 0; i < src.length; ++i) - if (typeof src[i] === 'object') - tgt.push(JSROOT.clone(src[i], map)); - else - tgt.push(src[i]); - - return tgt; - } - - // process typed array - if ((proto.indexOf('[object ') === 0) && (proto.indexOf('Array]') === proto.length - 6)) { - var tgt = []; - map.obj.push(src); - map.clones.push(tgt); - for (var i = 0; i < src.length; ++i) - tgt.push(src[i]); - - return tgt; - } - - var tgt = {}; - map.obj.push(src); - map.clones.push(tgt); - - for (var k in src) { - if (typeof src[k] === 'object') - tgt[k] = JSROOT.clone(src[k], map); - else if (!map.nofunc || (typeof src[k] !== 'function')) - tgt[k] = src[k]; - } - - return tgt; - }; - - /** - * @summary Clear all functions from the contained objects - * - * Only such objects can be cloned when transfer to Worker or converted into JSON - * @param {object} src object where functions will be removed - * @returns {object} same object after all functions are removed - * @private - */ - JSROOT.clear_func = function (src, map) { - if (src === null) return src; - - var proto = Object.prototype.toString.apply(src); - - if (proto === '[object Array]') { - for (var n = 0; n < src.length; n++) - if (typeof src[n] === 'object') - JSROOT.clear_func(src[n], map); - return src; - } - - if ((proto.indexOf('[object ') === 0) && (proto.indexOf('Array]') === proto.length - 6)) return src; - - if (!map) map = []; - var nomap = (map.length === 0); - if ('__clean_func__' in src) return src; - - map.push(src); - src['__clean_func__'] = true; - - for (var k in src) { - if (typeof src[k] === 'object') - JSROOT.clear_func(src[k], map); - else if (typeof src[k] === 'function') delete src[k]; - } - - if (nomap) - for (var n = 0; n < map.length; ++n) - delete map[n]['__clean_func__']; - - return src; - }; - - /** - * @summary Parse JSON code produced with TBufferJSON. - * - * @param {string} json string to parse - * @return {object|null} returns parsed object - */ - JSROOT.parse = function (json) { - if (!json) return null; - let obj = JSON.parse(json); - if (obj) obj = this.JSONR_unref(obj); - return obj; - }; - - /** - * @summary Parse multi.json request results - * @desc Method should be used to parse JSON code, produced by multi.json request of THttpServer - * - * @param {string} json string to parse - * @return {Array|null} returns array of parsed elements - */ - JSROOT.parse_multi = function (json) { - if (!json) return null; - var arr = JSON.parse(json); - if (arr && arr.length) - for (var i = 0; i < arr.length; ++i) - arr[i] = this.JSONR_unref(arr[i]); - return arr; - }; - - /** - * @summary Method converts JavaScript object into ROOT-like JSON - * - * @desc Produced JSON can be used in JSROOT.parse() again - * When performed properly, JSON can be used in TBufferJSON to read data back with C++ - */ - JSROOT.toJSON = function (obj) { - if (!obj || typeof obj !== 'object') return ""; - - var map = []; // map of stored objects - - function copy_value(value) { - if (typeof value === "function") return undefined; - - if ((value === undefined) || (value === null) || (typeof value !== 'object')) return value; - - var proto = Object.prototype.toString.apply(value); - - // typed array need to be converted into normal array, otherwise looks strange - if ((proto.indexOf('[object ') === 0) && (proto.indexOf('Array]') === proto.length - 6)) { - var arr = new Array(value.length); - for (var i = 0; i < value.length; ++i) - arr[i] = copy_value(value[i]); - return arr; - } - - // this is how reference is code - var refid = map.indexOf(value); - if (refid >= 0) return {$ref: refid}; - - var ks = Object.keys(value), len = ks.length, tgt = {}; - - if ((len === 3) && (ks[0] === '$pair') && (ks[1] === 'first') && (ks[2] === 'second')) { - // special handling of pair objects which does not included into objects map - tgt.$pair = value.$pair; - tgt.first = copy_value(value.first); - tgt.second = copy_value(value.second); - return tgt; - } - - map.push(value); - - for (var k = 0; k < len; ++k) { - var name = ks[k]; - tgt[name] = copy_value(value[name]); - } - - return tgt; - } - - var tgt = copy_value(obj); - - return JSON.stringify(tgt); - }; - - /** - * @summary Analyzes document.URL and extracts options after '?' mark - * - * @desc Following options supported ?opt1&opt2=3 - * In case of opt1 empty string will be returned, in case of opt2 '3' - * If option not found, null is returned (or default value value is provided) - * - * @param {string} opt option to search - * @param {string} full URL with options, document.URL will be used when not specified - * @returns {string|null} found value - * @private - */ - JSROOT.GetUrlOption = function (opt, url, dflt) { - - if (dflt === undefined) dflt = null; - if ((opt === null) || (typeof opt != 'string') || (opt.length === 0)) return dflt; - - if (!url) { - if (JSROOT.gStyle.IgnoreUrlOptions || (typeof document === 'undefined')) return dflt; - url = document.URL; - } - - var pos = url.indexOf("?"), nquotes; - if (pos < 0) return dflt; - url = decodeURI(url.slice(pos + 1)); - - while (url.length > 0) { - - if (url === opt) return ""; - - // try to correctly handle quotes in the URL - pos = 0; - nquotes = 0; - while ((pos < url.length) && ((nquotes !== 0) || (url[pos] !== "&"))) { - switch (url[pos]) { - case "'": - if (nquotes >= 0) nquotes = (nquotes + 1) % 2; - break; - case '"': - if (nquotes <= 0) nquotes = (nquotes - 1) % 2; - break; - } - pos++; - } - - if (url.indexOf(opt) === 0) { - if (url[opt.length] === "&") return ""; - - if (url[opt.length] === "=") { - url = url.slice(opt.length + 1, pos); - if (((url[0] === "'") || (url[0] === '"')) && (url[0] === url[url.length - 1])) url = url.substr(1, url.length - 2); - return url; - } - } - - url = url.substr(pos + 1); - } - return dflt; - }; - - /** - * @summary Parse string value as array. - * - * @desc It could be just simple string: "value" or - * array with or without string quotes: [element], ['elem1',elem2] - * - * @private - */ - JSROOT.ParseAsArray = function (val) { - - var res = []; - - if (typeof val != 'string') return res; - - val = val.trim(); - if (val === "") return res; - - // return as array with single element - if ((val.length < 2) || (val[0] !== '[') || (val[val.length - 1] !== ']')) { - res.push(val); - return res; - } - - // try to split ourself, checking quotes and brackets - var nbr = 0, nquotes = 0, ndouble = 0, last = 1; - - for (var indx = 1; indx < val.length; ++indx) { - if (nquotes > 0) { - if (val[indx] === "'") nquotes--; - continue; - } - if (ndouble > 0) { - if (val[indx] === '"') ndouble--; - continue; - } - switch (val[indx]) { - case "'": - nquotes++; - break; - case '"': - ndouble++; - break; - case "[": - nbr++; - break; - case "]": - if (indx < val.length - 1) { - nbr--; - break; - } - case ",": - if (nbr === 0) { - var sub = val.substring(last, indx).trim(); - if ((sub.length > 1) && (sub[0] === sub[sub.length - 1]) && ((sub[0] === '"') || (sub[0] === "'"))) - sub = sub.substr(1, sub.length - 2); - res.push(sub); - last = indx + 1; - } - break; - } - } - - if (res.length === 0) - res.push(val.substr(1, val.length - 2).trim()); - - return res; - }; - - /** - * @summary Special handling of URL options to produce array. - * - * @desc If normal option is specified ...?opt=abc, than array with single element will be created - * one could specify normal JSON array ...?opts=['item1','item2'] - * but also one could skip quotes ...?opts=[item1,item2] - * @private - */ - JSROOT.GetUrlOptionAsArray = function (opt, url) { - - var res = []; - - while (opt.length > 0) { - var separ = opt.indexOf(";"); - var part = (separ > 0) ? opt.substr(0, separ) : opt; - - if (separ > 0) opt = opt.substr(separ + 1); else opt = ""; - - var canarray = true; - if (part[0] === '#') { - part = part.substr(1); - canarray = false; - } - - var val = this.GetUrlOption(part, url, null); - - if (canarray) res = res.concat(JSROOT.ParseAsArray(val)); - else if (val !== null) res.push(val); - } - return res; - }; - - /** - * @summary Find function with given name. - * - * @desc Function name may include several namespaces like 'JSROOT.Painter.drawFrame' - * - * @private - */ - JSROOT.findFunction = function (name) { - if (typeof name === 'function') return name; - if (typeof name !== 'string') return null; - var names = name.split('.'), elem = null; - if (typeof window === 'object') elem = window; - if (names[0] === 'JSROOT') { - elem = this; - names.shift(); - } - - for (var n = 0; elem && (n < names.length); ++n) - elem = elem[names[n]]; - - return (typeof elem == 'function') ? elem : null; - }; - - /** - * @summary Generic method to invoke callback function. - * - * @param {object|function} func either normal function or container like - * { obj: object_pointer, func: name of method to call } - * @param arg1 first optional argument of callback - * @param arg2 second optional argument of callback - * - * @private - */ - JSROOT.CallBack = function (func, arg1, arg2) { - - if (typeof func == 'string') func = JSROOT.findFunction(func); - - if (!func) return; - - if (typeof func == 'function') return func(arg1, arg2); - - if (typeof func != 'object') return; - - if (('obj' in func) && ('func' in func) && - (typeof func.obj == 'object') && (typeof func.func == 'string') && - (typeof func.obj[func.func] == 'function')) { - return func.obj[func.func](arg1, arg2); - } - }; - - - // function can be used to open ROOT file, I/O functionality will be loaded when missing - JSROOT.OpenFile = function (filename, callback) { - JSROOT.AssertPrerequisites("io", function () { - JSROOT.OpenFile(filename, callback); - }); - }; - - // function can be used to draw supported ROOT classes, - // required functionality will be loaded automatically - // if painter pointer required, one should load '2d' functionality itself - // or use callback function which provides painter pointer as first argument - // defined in JSRootPainter.js - JSROOT.draw = function (divid, obj, opt, callback) { - JSROOT.AssertPrerequisites("2d", function () { - JSROOT.draw(divid, obj, opt, callback); - }); - }; - - // redraw object on given element - // defined in JSRootPainter.js - JSROOT.redraw = function (divid, obj, opt, callback) { - JSROOT.AssertPrerequisites("2d", function () { - JSROOT.redraw(divid, obj, opt, callback); - }); - }; - - // Create SVG, defined in JSRootPainter.js - JSROOT.MakeSVG = function (args, callback) { - JSROOT.AssertPrerequisites("2d", function () { - JSROOT.MakeSVG(args, callback); - }); - }; - - /** @summary Method to build JSROOT GUI with browser - * @private - */ - JSROOT.BuildSimpleGUI = function (user_scripts, andThen) { - if (typeof user_scripts == 'function') { - andThen = user_scripts; - user_scripts = null; - } - - var debugout = null, - nobrowser = JSROOT.GetUrlOption('nobrowser') != null, - requirements = "2d;hierarchy;", - simplegui = document.getElementById('simpleGUI'); - - if (JSROOT.GetUrlOption('libs') !== null) JSROOT.use_full_libs = true; - - if (simplegui) { - debugout = 'simpleGUI'; - if (JSROOT.GetUrlOption('file') || JSROOT.GetUrlOption('files')) requirements += "io;"; - if (simplegui.getAttribute('nobrowser') && (simplegui.getAttribute('nobrowser') !== "false")) nobrowser = true; - } else if (document.getElementById('onlineGUI')) { - debugout = 'onlineGUI'; - } else if (document.getElementById('drawGUI')) { - debugout = 'drawGUI'; - nobrowser = true; - } else { - requirements += "io;"; - } - - if (user_scripts === 'check_existing_elements') { - user_scripts = null; - if (debugout == null) return; - } - - if (!nobrowser) requirements += 'jq2d;'; - - if (!user_scripts) user_scripts = JSROOT.GetUrlOption("autoload") || JSROOT.GetUrlOption("load"); - - if (user_scripts) requirements += "load:" + user_scripts + ";"; - - JSROOT.AssertPrerequisites(requirements, function () { - JSROOT.CallBack(JSROOT.findFunction(nobrowser ? 'JSROOT.BuildNobrowserGUI' : 'JSROOT.BuildGUI')); - JSROOT.CallBack(andThen); - }, debugout); - }; - - /** @summary Create some ROOT classes - * - * @param {string} typename - ROOT class name - * @example - * var obj = JSROOT.Create("TNamed"); - * obj.fName = "name"; - * obj.fTitle = "title"; - */ - JSROOT.Create = function (typename, target) { - var obj = target || {}; - - switch (typename) { - case 'TObject': - JSROOT.extend(obj, {fUniqueID: 0, fBits: 0x3000008}); - break; - case 'TNamed': - JSROOT.extend(obj, {fUniqueID: 0, fBits: 0x3000008, fName: "", fTitle: ""}); - break; - case 'TList': - case 'THashList': - JSROOT.extend(obj, {name: typename, arr: [], opt: []}); - break; - case 'TAttAxis': - JSROOT.extend(obj, { - fNdivisions: 510, fAxisColor: 1, - fLabelColor: 1, fLabelFont: 42, fLabelOffset: 0.005, fLabelSize: 0.035, fTickLength: 0.03, - fTitleOffset: 1, fTitleSize: 0.035, fTitleColor: 1, fTitleFont: 42 - }); - break; - case 'TAxis': - JSROOT.Create("TNamed", obj); - JSROOT.Create("TAttAxis", obj); - JSROOT.extend(obj, { - fNbins: 0, fXmin: 0, fXmax: 0, fXbins: [], fFirst: 0, fLast: 0, - fBits2: 0, fTimeDisplay: false, fTimeFormat: "", fLabels: null, fModLabs: null - }); - break; - case 'TAttLine': - JSROOT.extend(obj, {fLineColor: 1, fLineStyle: 1, fLineWidth: 1}); - break; - case 'TAttFill': - JSROOT.extend(obj, {fFillColor: 0, fFillStyle: 0}); - break; - case 'TAttMarker': - JSROOT.extend(obj, {fMarkerColor: 1, fMarkerStyle: 1, fMarkerSize: 1.}); - break; - case 'TLine': - JSROOT.Create("TObject", obj); - JSROOT.Create("TAttLine", obj); - JSROOT.extend(obj, {fX1: 0, fX2: 1, fY1: 0, fY2: 1}); - break; - case 'TBox': - JSROOT.Create("TObject", obj); - JSROOT.Create("TAttLine", obj); - JSROOT.Create("TAttFill", obj); - JSROOT.extend(obj, {fX1: 0, fX2: 1, fY1: 0, fY2: 1}); - break; - case 'TPave': - JSROOT.Create("TBox", obj); - JSROOT.extend(obj, { - fX1NDC: 0., fY1NDC: 0, fX2NDC: 1, fY2NDC: 1, - fBorderSize: 0, fInit: 1, fShadowColor: 1, - fCornerRadius: 0, fOption: "blNDC", fName: "title" - }); - break; - case 'TAttText': - JSROOT.extend(obj, {fTextAngle: 0, fTextSize: 0, fTextAlign: 22, fTextColor: 1, fTextFont: 42}); - break; - case 'TPaveText': - JSROOT.Create("TPave", obj); - JSROOT.Create("TAttText", obj); - JSROOT.extend(obj, {fLabel: "", fLongest: 27, fMargin: 0.05, fLines: JSROOT.Create("TList")}); - break; - case 'TPaveStats': - JSROOT.Create("TPaveText", obj); - JSROOT.extend(obj, {fOptFit: 0, fOptStat: 0, fFitFormat: "", fStatFormat: "", fParent: null}); - break; - case 'TLegend': - JSROOT.Create("TPave", obj); - JSROOT.Create("TAttText", obj); - JSROOT.extend(obj, { - fColumnSeparation: 0, - fEntrySeparation: 0.1, - fMargin: 0.25, - fNColumns: 1, - fPrimitives: JSROOT.Create("TList") - }); - break; - case 'TLegendEntry': - JSROOT.Create("TObject", obj); - JSROOT.Create("TAttText", obj); - JSROOT.Create("TAttLine", obj); - JSROOT.Create("TAttFill", obj); - JSROOT.Create("TAttMarker", obj); - JSROOT.extend(obj, {fLabel: "", fObject: null, fOption: ""}); - break; - case 'TText': - JSROOT.Create("TNamed", obj); - JSROOT.Create("TAttText", obj); - JSROOT.extend(obj, {fLimitFactorSize: 3, fOriginSize: 0.04}); - break; - case 'TLatex': - JSROOT.Create("TText", obj); - JSROOT.Create("TAttLine", obj); - JSROOT.extend(obj, {fX: 0, fY: 0}); - break; - case 'TObjString': - JSROOT.Create("TObject", obj); - JSROOT.extend(obj, {fString: ""}); - break; - case 'TH1': - JSROOT.Create("TNamed", obj); - JSROOT.Create("TAttLine", obj); - JSROOT.Create("TAttFill", obj); - JSROOT.Create("TAttMarker", obj); - - JSROOT.extend(obj, { - fNcells: 0, - fXaxis: JSROOT.Create("TAxis"), - fYaxis: JSROOT.Create("TAxis"), - fZaxis: JSROOT.Create("TAxis"), - fBarOffset: 0, fBarWidth: 1000, fEntries: 0., - fTsumw: 0., fTsumw2: 0., fTsumwx: 0., fTsumwx2: 0., - fMaximum: -1111., fMinimum: -1111, fNormFactor: 0., fContour: [], - fSumw2: [], fOption: "", - fFunctions: JSROOT.Create("TList"), - fBufferSize: 0, fBuffer: [], fBinStatErrOpt: 0, fStatOverflows: 2 - }); - break; - case 'TH1I': - case 'TH1F': - case 'TH1D': - case 'TH1S': - case 'TH1C': - JSROOT.Create("TH1", obj); - obj.fArray = []; - break; - case 'TH2': - JSROOT.Create("TH1", obj); - JSROOT.extend(obj, {fScalefactor: 1., fTsumwy: 0., fTsumwy2: 0, fTsumwxy: 0}); - break; - case 'TH2I': - case 'TH2F': - case 'TH2D': - case 'TH2S': - case 'TH2C': - JSROOT.Create("TH2", obj); - obj.fArray = []; - break; - case 'TH3': - JSROOT.Create("TH1", obj); - JSROOT.extend(obj, { - fTsumwy: 0., - fTsumwy2: 0, - fTsumwz: 0., - fTsumwz2: 0, - fTsumwxy: 0, - fTsumwxz: 0, - fTsumwyz: 0 - }); - break; - case 'TH3I': - case 'TH3F': - case 'TH3D': - case 'TH3S': - case 'TH3C': - JSROOT.Create("TH3", obj); - obj.fArray = []; - break; - case 'THStack': - JSROOT.Create("TNamed", obj); - JSROOT.extend(obj, { - fHists: JSROOT.Create("TList"), - fHistogram: null, - fMaximum: -1111, - fMinimum: -1111 - }); - break; - case 'TGraph': - JSROOT.Create("TNamed", obj); - JSROOT.Create("TAttLine", obj); - JSROOT.Create("TAttFill", obj); - JSROOT.Create("TAttMarker", obj); - JSROOT.extend(obj, { - fFunctions: JSROOT.Create("TList"), fHistogram: null, - fMaxSize: 0, fMaximum: -1111, fMinimum: -1111, fNpoints: 0, fX: [], fY: [] - }); - break; - case 'TGraphAsymmErrors': - JSROOT.Create("TGraph", obj); - JSROOT.extend(obj, {fEXlow: [], fEXhigh: [], fEYlow: [], fEYhigh: []}); - break; - case 'TMultiGraph': - JSROOT.Create("TNamed", obj); - JSROOT.extend(obj, { - fFunctions: JSROOT.Create("TList"), fGraphs: JSROOT.Create("TList"), - fHistogram: null, fMaximum: -1111, fMinimum: -1111 - }); - break; - case 'TGraphPolargram': - JSROOT.Create("TNamed", obj); - JSROOT.Create("TAttText", obj); - JSROOT.Create("TAttLine", obj); - JSROOT.extend(obj, { - fRadian: true, - fDegree: false, - fGrad: false, - fPolarLabelColor: 1, - fRadialLabelColor: 1, - fAxisAngle: 0, - fPolarOffset: 0.04, - fPolarTextSize: 0.04, - fRadialOffset: 0.025, - fRadialTextSize: 0.035, - fRwrmin: 0, - fRwrmax: 1, - fRwtmin: 0, - fRwtmax: 2 * Math.PI, - fTickpolarSize: 0.02, - fPolarLabelFont: 62, - fRadialLabelFont: 62, - fCutRadial: 0, - fNdivRad: 508, - fNdivPol: 508 - }); - break; - case 'TPolyLine': - JSROOT.Create("TObject", obj); - JSROOT.Create("TAttLine", obj); - JSROOT.Create("TAttFill", obj); - JSROOT.extend(obj, {fLastPoint: -1, fN: 0, fOption: "", fX: null, fY: null}); - break; - case 'TGaxis': - JSROOT.Create("TLine", obj); - JSROOT.Create("TAttText", obj); - JSROOT.extend(obj, { - fChopt: "", fFunctionName: "", fGridLength: 0, - fLabelColor: 1, fLabelFont: 42, fLabelOffset: 0.005, fLabelSize: 0.035, - fName: "", fNdiv: 12, fTickSize: 0.02, fTimeFormat: "", - fTitle: "", fTitleOffset: 1, fTitleSize: 0.035, - fWmax: 100, fWmin: 0 - }); - break; - case 'TAttPad': - JSROOT.extend(obj, { - fLeftMargin: JSROOT.gStyle.fPadLeftMargin, - fRightMargin: JSROOT.gStyle.fPadRightMargin, - fBottomMargin: JSROOT.gStyle.fPadBottomMargin, - fTopMargin: JSROOT.gStyle.fPadTopMargin, - fXfile: 2, fYfile: 2, fAfile: 1, fXstat: 0.99, fYstat: 0.99, fAstat: 2, - fFrameFillColor: JSROOT.gStyle.fFrameFillColor, - fFrameFillStyle: JSROOT.gStyle.fFrameFillStyle, - fFrameLineColor: JSROOT.gStyle.fFrameLineColor, - fFrameLineWidth: JSROOT.gStyle.fFrameLineWidth, - fFrameLineStyle: JSROOT.gStyle.fFrameLineStyle, - fFrameBorderSize: JSROOT.gStyle.fFrameBorderSize, - fFrameBorderMode: JSROOT.gStyle.fFrameBorderMode - }); - break; - case 'TPad': - JSROOT.Create("TObject", obj); - JSROOT.Create("TAttLine", obj); - JSROOT.Create("TAttFill", obj); - JSROOT.Create("TAttPad", obj); - JSROOT.extend(obj, { - fX1: 0, - fY1: 0, - fX2: 1, - fY2: 1, - fXtoAbsPixelk: 1, - fXtoPixelk: 1, - fXtoPixel: 1, - fYtoAbsPixelk: 1, - fYtoPixelk: 1, - fYtoPixel: 1, - fUtoAbsPixelk: 1, - fUtoPixelk: 1, - fUtoPixel: 1, - fVtoAbsPixelk: 1, - fVtoPixelk: 1, - fVtoPixel: 1, - fAbsPixeltoXk: 1, - fPixeltoXk: 1, - fPixeltoX: 1, - fAbsPixeltoYk: 1, - fPixeltoYk: 1, - fPixeltoY: 1, - fXlowNDC: 0, - fYlowNDC: 0, - fXUpNDC: 0, - fYUpNDC: 0, - fWNDC: 1, - fHNDC: 1, - fAbsXlowNDC: 0, - fAbsYlowNDC: 0, - fAbsWNDC: 1, - fAbsHNDC: 1, - fUxmin: 0, - fUymin: 0, - fUxmax: 0, - fUymax: 0, - fTheta: 30, - fPhi: 30, - fAspectRatio: 0, - fNumber: 0, - fLogx: JSROOT.gStyle.fOptLogx, - fLogy: JSROOT.gStyle.fOptLogy, - fLogz: JSROOT.gStyle.fOptLogz, - fTickx: JSROOT.gStyle.fPadTickX, - fTicky: JSROOT.gStyle.fPadTickY, - fPadPaint: 0, - fCrosshair: 0, - fCrosshairPos: 0, - fBorderSize: 2, - fBorderMode: 0, - fModified: false, - fGridx: JSROOT.gStyle.fPadGridX, - fGridy: JSROOT.gStyle.fPadGridY, - fAbsCoord: false, - fEditable: true, - fFixedAspectRatio: false, - fPrimitives: JSROOT.Create("TList"), - fExecs: null, - fName: "pad", - fTitle: "canvas" - }); - - break; - case 'TAttCanvas': - JSROOT.extend(obj, { - fXBetween: 2, fYBetween: 2, fTitleFromTop: 1.2, - fXdate: 0.2, fYdate: 0.3, fAdate: 1 - }); - break; - case 'TCanvas': - JSROOT.Create("TPad", obj); - JSROOT.extend(obj, { - fNumPaletteColor: 0, fNextPaletteColor: 0, fDISPLAY: "$DISPLAY", - fDoubleBuffer: 0, fRetained: true, fXsizeUser: 0, - fYsizeUser: 0, fXsizeReal: 20, fYsizeReal: 10, - fWindowTopX: 0, fWindowTopY: 0, fWindowWidth: 0, fWindowHeight: 0, - fCw: 500, fCh: 300, fCatt: JSROOT.Create("TAttCanvas"), - kMoveOpaque: true, kResizeOpaque: true, fHighLightColor: 5, - fBatch: true, kShowEventStatus: false, kAutoExec: true, kMenuBar: true - }); - break; - case 'TGeoVolume': - JSROOT.Create("TNamed", obj); - JSROOT.Create("TAttLine", obj); - JSROOT.Create("TAttFill", obj); - JSROOT.extend(obj, { - fGeoAtt: 0, - fFinder: null, - fMedium: null, - fNodes: null, - fNtotal: 0, - fNumber: 0, - fRefCount: 0, - fShape: null, - fVoxels: null - }); - break; - case 'TGeoNode': - JSROOT.Create("TNamed", obj); - JSROOT.extend(obj, {fGeoAtt: 0, fMother: null, fNovlp: 0, fNumber: 0, fOverlaps: null, fVolume: null}); - break; - case 'TGeoNodeMatrix': - JSROOT.Create("TGeoNode", obj); - JSROOT.extend(obj, {fMatrix: null}); - break; - case 'TGeoTrack': - JSROOT.Create("TObject", obj); - JSROOT.Create("TAttLine", obj); - JSROOT.Create("TAttMarker", obj); - JSROOT.extend(obj, {fGeoAtt: 0, fNpoints: 0, fPoints: []}); - break; - } - - obj._typename = typename; - this.addMethods(obj); - return obj; - }; - - /** @summary Create TList - * @desc obsolete, use JSROOT.Create("TList") instead - * @deprecated */ - JSROOT.CreateTList = function () { - return JSROOT.Create("TList"); - }; - - /** @summary Create TAxis - * @desc obsolete, use JSROOT.Create("TAxis") instead - * @deprecated */ - JSROOT.CreateTAxis = function () { - return JSROOT.Create("TAxis"); - }; - - /** @summary Create histogram object - * @param {string} typename - histogram typename like TH1I or TH2F - * @param {number} nbinsx - number of bins on X-axis - * @param {number} [nbinsy] - number of bins on Y-axis (for 2D/3D histograms) - * @param {number} [nbinsz] - number of bins on Z-axis (for 3D histograms) - */ - JSROOT.CreateHistogram = function (typename, nbinsx, nbinsy, nbinsz) { - // create histogram object of specified type - // if bins numbers are specified, appropriate typed array will be created - var histo = JSROOT.Create(typename); - if (!histo.fXaxis || !histo.fYaxis || !histo.fZaxis) return null; - histo.fName = "hist"; - histo.fTitle = "title"; - if (nbinsx) JSROOT.extend(histo.fXaxis, {fNbins: nbinsx, fXmin: 0, fXmax: nbinsx}); - if (nbinsy) JSROOT.extend(histo.fYaxis, {fNbins: nbinsy, fXmin: 0, fXmax: nbinsy}); - if (nbinsz) JSROOT.extend(histo.fZaxis, {fNbins: nbinsz, fXmin: 0, fXmax: nbinsz}); - switch (parseInt(typename[2])) { - case 1: - if (nbinsx) histo.fNcells = nbinsx + 2; - break; - case 2: - if (nbinsx && nbinsy) histo.fNcells = (nbinsx + 2) * (nbinsy + 2); - break; - case 3: - if (nbinsx && nbinsy && nbinsz) histo.fNcells = (nbinsx + 2) * (nbinsy + 2) * (nbinsz + 2); - break; - } - if (histo.fNcells > 0) { - switch (typename[3]) { - case "C" : - histo.fArray = new Int8Array(histo.fNcells); - break; - case "S" : - histo.fArray = new Int16Array(histo.fNcells); - break; - case "I" : - histo.fArray = new Int32Array(histo.fNcells); - break; - case "F" : - histo.fArray = new Float32Array(histo.fNcells); - break; - case "L" : - histo.fArray = new Float64Array(histo.fNcells); - break; - case "D" : - histo.fArray = new Float64Array(histo.fNcells); - break; - default: - histo.fArray = new Array(histo.fNcells); - break; - } - for (var i = 0; i < histo.fNcells; ++i) histo.fArray[i] = 0; - } - return histo; - }; - - /** @summary Create 1-d histogram - * @desc obsolete, use JSROOT.CreateHistogram() instead - * @deprecated */ - JSROOT.CreateTH1 = function (nbinsx) { - return JSROOT.CreateHistogram("TH1I", nbinsx); - }; - - /** @summary Create 2-d histogram - * @desc obsolete, use JSROOT.CreateHistogram() instead - * @deprecated */ - JSROOT.CreateTH2 = function (nbinsx, nbinsy) { - return JSROOT.CreateHistogram("TH2I", nbinsx, nbinsy); - }; - - /** @summary Create 3-d histogram - * @desc obsolete, use JSROOT.CreateHistogram() instead - * @deprecated */ - JSROOT.CreateTH3 = function (nbinsx, nbinsy, nbinsz) { - return JSROOT.CreateHistogram("TH3I", nbinsx, nbinsy, nbinsz); - }; - - /** @summary Creates TPolyLine object - * @param {number} npoints - number of points - * @param {boolean} [use_int32] - use Int32Array type for points, default is Float32Array */ - JSROOT.CreateTPolyLine = function (npoints, use_int32) { - var poly = JSROOT.Create("TPolyLine"); - if (npoints) { - poly.fN = npoints; - if (use_int32) { - poly.fX = new Int32Array(npoints); - poly.fY = new Int32Array(npoints); - } else { - poly.fX = new Float32Array(npoints); - poly.fY = new Float32Array(npoints); - } - } - - return poly; - }; - - /** @summary Creates TGraph object - * @param {number} npoints - number of points in TGraph - * @param {array} [xpts] - array with X coordinates - * @param {array} [ypts] - array with Y coordinates */ - JSROOT.CreateTGraph = function (npoints, xpts, ypts) { - var graph = JSROOT.extend(JSROOT.Create("TGraph"), {fBits: 0x3000408, fName: "graph", fTitle: "title"}); - - if (npoints > 0) { - graph.fMaxSize = graph.fNpoints = npoints; - - var usex = (typeof xpts == 'object') && (xpts.length === npoints); - var usey = (typeof ypts == 'object') && (ypts.length === npoints); - - for (var i = 0; i < npoints; ++i) { - graph.fX.push(usex ? xpts[i] : i / npoints); - graph.fY.push(usey ? ypts[i] : i / npoints); - } - } - - return graph; - }; - - /** @summary Creates THStack object - * @desc - * As arguments one could specify any number of histograms objects - * @example - * var nbinsx = 20; - * var h1 = JSROOT.CreateHistogram("TH1F", nbinsx); - * var h2 = JSROOT.CreateHistogram("TH1F", nbinsx); - * var h3 = JSROOT.CreateHistogram("TH1F", nbinsx); - * var stack = JSROOT.CreateTHStack(h1, h2, h3); - * */ - JSROOT.CreateTHStack = function () { - var stack = JSROOT.Create("THStack"); - for (var i = 0; i < arguments.length; ++i) - stack.fHists.Add(arguments[i], ""); - return stack; - }; - - /** @summary Creates TMultiGraph object - * @desc - * As arguments one could specify any number of TGraph objects */ - JSROOT.CreateTMultiGraph = function () { - var mgraph = JSROOT.Create("TMultiGraph"); - for (var i = 0; i < arguments.length; ++i) - mgraph.fGraphs.Add(arguments[i], ""); - return mgraph; - }; - - JSROOT.methodsCache = {}; // variable used to keep methods for known classes - - /** @summary Returns methods for given typename - * @private - */ - JSROOT.getMethods = function (typename, obj) { - - var m = JSROOT.methodsCache[typename], - has_methods = (m !== undefined); - - if (!has_methods) m = {}; - - // Due to binary I/O such TObject methods may not be set for derived classes - // Therefore when methods requested for given object, check also that basic methods are there - if ((typename === "TObject") || (typename === "TNamed") || (obj && (obj.fBits !== undefined))) - if (m.TestBit === undefined) { - m.TestBit = function (f) { - return (this.fBits & f) !== 0; - }; - m.InvertBit = function (f) { - this.fBits = this.fBits ^ (f & 0xffffff); - }; - } - - if (has_methods) return m; - - if ((typename === 'TList') || (typename === 'THashList')) { - m.Clear = function () { - this.arr = []; - this.opt = []; - }; - m.Add = function (obj, opt) { - this.arr.push(obj); - this.opt.push((opt && typeof opt == 'string') ? opt : ""); - }; - m.AddFirst = function (obj, opt) { - this.arr.unshift(obj); - this.opt.unshift((opt && typeof opt == 'string') ? opt : ""); - }; - m.RemoveAt = function (indx) { - this.arr.splice(indx, 1); - this.opt.splice(indx, 1); - } - } - - if ((typename === "TPaveText") || (typename === "TPaveStats")) { - m.AddText = function (txt) { - // this.fLines.Add({ _typename: 'TLatex', fTitle: txt, fTextColor: 1 }); - var line = JSROOT.Create("TLatex"); - line.fTitle = txt; - this.fLines.Add(line); - }; - m.Clear = function () { - this.fLines.Clear(); - } - } - - if ((typename.indexOf("TF1") === 0) || (typename === "TF2")) { - m.addFormula = function (obj) { - if (!obj) return; - if (this.formulas === undefined) this.formulas = []; - this.formulas.push(obj); - }; - - m.evalPar = function (x, y) { - if (!('_func' in this) || (this._title !== this.fTitle)) { - - var _func = this.fTitle, isformula = false, pprefix = "["; - if (_func === "gaus") _func = "gaus(0)"; - if (this.fFormula && typeof this.fFormula.fFormula == "string") { - if (this.fFormula.fFormula.indexOf("[](double*x,double*p)") === 0) { - isformula = true; - pprefix = "p["; - _func = this.fFormula.fFormula.substr(21); - } else { - _func = this.fFormula.fFormula; - pprefix = "[p"; - } - if (this.fFormula.fClingParameters && this.fFormula.fParams) { - for (var i = 0; i < this.fFormula.fParams.length; ++i) { - var regex = new RegExp('(\\[' + this.fFormula.fParams[i].first + '\\])', 'g'), - parvalue = this.fFormula.fClingParameters[this.fFormula.fParams[i].second]; - _func = _func.replace(regex, (parvalue < 0) ? "(" + parvalue + ")" : parvalue); - } - } - } - - if ('formulas' in this) - for (var i = 0; i < this.formulas.length; ++i) - while (_func.indexOf(this.formulas[i].fName) >= 0) - _func = _func.replace(this.formulas[i].fName, this.formulas[i].fTitle); - _func = _func.replace(/\b(abs)\b/g, 'TMath::Abs') - .replace(/TMath::Exp\(/g, 'Math.exp(') - .replace(/TMath::Abs\(/g, 'Math.abs('); - if (typeof JSROOT.Math == 'object') { - this._math = JSROOT.Math; - _func = _func.replace(/TMath::Prob\(/g, 'this._math.Prob(') - .replace(/TMath::Gaus\(/g, 'this._math.Gaus(') - .replace(/TMath::BreitWigner\(/g, 'this._math.BreitWigner(') - .replace(/xygaus\(/g, 'this._math.gausxy(this, x, y, ') - .replace(/gaus\(/g, 'this._math.gaus(this, x, ') - .replace(/gausn\(/g, 'this._math.gausn(this, x, ') - .replace(/expo\(/g, 'this._math.expo(this, x, ') - .replace(/landau\(/g, 'this._math.landau(this, x, ') - .replace(/landaun\(/g, 'this._math.landaun(this, x, ') - .replace(/ROOT::Math::/g, 'this._math.'); - } - for (var i = 0; i < this.fNpar; ++i) { - var parname = pprefix + i + "]"; - while (_func.indexOf(parname) !== -1) - _func = _func.replace(parname, '(' + this.GetParValue(i) + ')'); - } - _func = _func.replace(/\b(sin)\b/gi, 'Math.sin') - .replace(/\b(cos)\b/gi, 'Math.cos') - .replace(/\b(tan)\b/gi, 'Math.tan') - .replace(/\b(exp)\b/gi, 'Math.exp') - .replace(/\b(pow)\b/gi, 'Math.pow') - .replace(/pi/g, 'Math.PI'); - for (var n = 2; n < 10; ++n) - _func = _func.replace('x^' + n, 'Math.pow(x,' + n + ')'); - - if (isformula) { - _func = _func.replace(/x\[0\]/g, "x"); - if (this._typename === "TF2") { - _func = _func.replace(/x\[1\]/g, "y"); - this._func = new Function("x", "y", _func).bind(this); - } else { - this._func = new Function("x", _func).bind(this); - } - } else if (this._typename === "TF2") - this._func = new Function("x", "y", "return " + _func).bind(this); - else - this._func = new Function("x", "return " + _func).bind(this); - - this._title = this.fTitle; - } - - return this._func(x, y); - }; - m.GetParName = function (n) { - if (this.fFormula && this.fFormula.fParams) return this.fFormula.fParams[n].first; - if (this.fNames && this.fNames[n]) return this.fNames[n]; - return "p" + n; - }; - m.GetParValue = function (n) { - if (this.fFormula && this.fFormula.fClingParameters) return this.fFormula.fClingParameters[n]; - if (this.fParams) return this.fParams[n]; - return undefined; - }; - m.GetParError = function (n) { - return this.fParErrors ? this.fParErrors[n] : undefined; - }; - m.GetNumPars = function () { - return this.fNpar; - } - } - - if (((typename.indexOf("TGraph") === 0) || (typename === "TCutG")) && (typename !== "TGraphPolargram") && (typename !== "TGraphTime")) { - // check if point inside figure specified by the TGraph - m.IsInside = function (xp, yp) { - var i, j = this.fNpoints - 1, x = this.fX, y = this.fY, oddNodes = false; - - for (i = 0; i < this.fNpoints; ++i) { - if ((y[i] < yp && y[j] >= yp) || (y[j] < yp && y[i] >= yp)) { - if (x[i] + (yp - y[i]) / (y[j] - y[i]) * (x[j] - x[i]) < xp) { - oddNodes = !oddNodes; - } - } - j = i; - } - - return oddNodes; - }; - } - - if (typename.indexOf("TH1") === 0 || - typename.indexOf("TH2") === 0 || - typename.indexOf("TH3") === 0) { - m.getBinError = function (bin) { - // -*-*-*-*-*Return value of error associated to bin number bin*-*-*-*-* - // if the sum of squares of weights has been defined (via Sumw2), - // this function returns the sqrt(sum of w2). - // otherwise it returns the sqrt(contents) for this bin. - if (bin >= this.fNcells) bin = this.fNcells - 1; - if (bin < 0) bin = 0; - if (bin < this.fSumw2.length) - return Math.sqrt(this.fSumw2[bin]); - return Math.sqrt(Math.abs(this.fArray[bin])); - }; - m.setBinContent = function (bin, content) { - // Set bin content - only trivial case, without expansion - this.fEntries++; - this.fTsumw = 0; - if ((bin >= 0) && (bin < this.fArray.length)) - this.fArray[bin] = content; - }; - } - - if (typename.indexOf("TH1") === 0) { - m.getBin = function (x) { - return x; - }; - m.getBinContent = function (bin) { - return this.fArray[bin]; - }; - m.Fill = function (x, weight) { - var axis = this.fXaxis, - bin = 1 + Math.floor((x - axis.fXmin) / (axis.fXmax - axis.fXmin) * axis.fNbins); - if (bin < 0) bin = 0; else if (bin > axis.fNbins + 1) bin = axis.fNbins + 1; - this.fArray[bin] += ((weight === undefined) ? 1 : weight); - } - } - - if (typename.indexOf("TH2") === 0) { - m.getBin = function (x, y) { - return (x + (this.fXaxis.fNbins + 2) * y); - }; - m.getBinContent = function (x, y) { - return this.fArray[this.getBin(x, y)]; - }; - m.Fill = function (x, y, weight) { - var axis1 = this.fXaxis, axis2 = this.fYaxis, - bin1 = 1 + Math.floor((x - axis1.fXmin) / (axis1.fXmax - axis1.fXmin) * axis1.fNbins), - bin2 = 1 + Math.floor((y - axis2.fXmin) / (axis2.fXmax - axis2.fXmin) * axis2.fNbins); - if (bin1 < 0) bin1 = 0; else if (bin1 > axis1.fNbins + 1) bin1 = axis1.fNbins + 1; - if (bin2 < 0) bin2 = 0; else if (bin2 > axis2.fNbins + 1) bin2 = axis2.fNbins + 1; - this.fArray[bin1 + (axis1.fNbins + 2) * bin2] += ((weight === undefined) ? 1 : weight); - } - } - - if (typename.indexOf("TH3") === 0) { - m.getBin = function (x, y, z) { - return (x + (this.fXaxis.fNbins + 2) * (y + (this.fYaxis.fNbins + 2) * z)); - }; - m.getBinContent = function (x, y, z) { - return this.fArray[this.getBin(x, y, z)]; - }; - m.Fill = function (x, y, z, weight) { - var axis1 = this.fXaxis, axis2 = this.fYaxis, axis3 = this.fZaxis, - bin1 = 1 + Math.floor((x - axis1.fXmin) / (axis1.fXmax - axis1.fXmin) * axis1.fNbins), - bin2 = 1 + Math.floor((y - axis2.fXmin) / (axis2.fXmax - axis2.fXmin) * axis2.fNbins), - bin3 = 1 + Math.floor((z - axis3.fXmin) / (axis3.fXmax - axis3.fXmin) * axis3.fNbins); - if (bin1 < 0) bin1 = 0; else if (bin1 > axis1.fNbins + 1) bin1 = axis1.fNbins + 1; - if (bin2 < 0) bin2 = 0; else if (bin2 > axis2.fNbins + 1) bin2 = axis2.fNbins + 1; - if (bin3 < 0) bin3 = 0; else if (bin3 > axis3.fNbins + 1) bin3 = axis3.fNbins + 1; - this.fArray[bin1 + (axis1.fNbins + 2) * (bin2 + (axis2.fNbins + 2) * bin3)] += ((weight === undefined) ? 1 : weight); - } - } - - if (typename.indexOf("TProfile") === 0) { - if (typename.indexOf("TProfile2D") === 0) { - m.getBin = function (x, y) { - return (x + (this.fXaxis.fNbins + 2) * y); - }; - m.getBinContent = function (x, y) { - var bin = this.getBin(x, y); - if (bin < 0 || bin >= this.fNcells) return 0; - if (this.fBinEntries[bin] < 1e-300) return 0; - if (!this.fArray) return 0; - return this.fArray[bin] / this.fBinEntries[bin]; - }; - m.getBinEntries = function (x, y) { - var bin = this.getBin(x, y); - if (bin < 0 || bin >= this.fNcells) return 0; - return this.fBinEntries[bin]; - } - } else { - m.getBin = function (x) { - return x; - }; - m.getBinContent = function (bin) { - if (bin < 0 || bin >= this.fNcells) return 0; - if (this.fBinEntries[bin] < 1e-300) return 0; - if (!this.fArray) return 0; - return this.fArray[bin] / this.fBinEntries[bin]; - }; - } - m.getBinEffectiveEntries = function (bin) { - if (bin < 0 || bin >= this.fNcells) return 0; - var sumOfWeights = this.fBinEntries[bin]; - if (!this.fBinSumw2 || this.fBinSumw2.length !== this.fNcells) { - // this can happen when reading an old file - return sumOfWeights; - } - var sumOfWeightsSquare = this.fBinSumw2[bin]; - return (sumOfWeightsSquare > 0) ? sumOfWeights * sumOfWeights / sumOfWeightsSquare : 0; - }; - m.getBinError = function (bin) { - if (bin < 0 || bin >= this.fNcells) return 0; - var cont = this.fArray[bin], // sum of bin w *y - sum = this.fBinEntries[bin], // sum of bin weights - err2 = this.fSumw2[bin], // sum of bin w * y^2 - neff = this.getBinEffectiveEntries(bin); // (sum of w)^2 / (sum of w^2) - if (sum < 1e-300) return 0; // for empty bins - var EErrorType = {kERRORMEAN: 0, kERRORSPREAD: 1, kERRORSPREADI: 2, kERRORSPREADG: 3}; - // case the values y are gaussian distributed y +/- sigma and w = 1/sigma^2 - if (this.fErrorMode === EErrorType.kERRORSPREADG) - return 1.0 / Math.sqrt(sum); - // compute variance in y (eprim2) and standard deviation in y (eprim) - var contsum = cont / sum, eprim = Math.sqrt(Math.abs(err2 / sum - contsum * contsum)); - if (this.fErrorMode === EErrorType.kERRORSPREADI) { - if (eprim !== 0) return eprim / Math.sqrt(neff); - // in case content y is an integer (so each my has an error +/- 1/sqrt(12) - // when the std(y) is zero - return 1.0 / Math.sqrt(12 * neff); - } - // if approximate compute the sums (of w, wy and wy2) using all the bins - // when the variance in y is zero - // case option "S" return standard deviation in y - if (this.fErrorMode === EErrorType.kERRORSPREAD) return eprim; - // default case : fErrorMode = kERRORMEAN - // return standard error on the mean of y - return (eprim / Math.sqrt(neff)); - }; - } - - if (typename === "TAxis") { - m.GetBinLowEdge = function (bin) { - if (this.fNbins <= 0) return 0; - if ((this.fXbins.length > 0) && (bin > 0) && (bin <= this.fNbins)) return this.fXbins[bin - 1]; - return this.fXmin + (bin - 1) * (this.fXmax - this.fXmin) / this.fNbins; - }; - m.GetBinCenter = function (bin) { - if (this.fNbins <= 0) return 0; - if ((this.fXbins.length > 0) && (bin > 0) && (bin < this.fNbins)) return (this.fXbins[bin - 1] + this.fXbins[bin]) / 2; - return this.fXmin + (bin - 0.5) * (this.fXmax - this.fXmin) / this.fNbins; - } - } - - if (typeof JSROOT.getMoreMethods == "function") - JSROOT.getMoreMethods(m, typename, obj); - - JSROOT.methodsCache[typename] = m; - return m; - }; - - /** @summary Returns true if object represents basic ROOT collections - * @private */ - JSROOT.IsRootCollection = function (lst, typename) { - if (lst && (typeof lst === 'object')) { - if ((lst.$kind === "TList") || (lst.$kind === "TObjArray")) return true; - if (!typename) typename = lst._typename; - } - if (!typename) return false; - return (typename === 'TList') || (typename === 'THashList') || (typename === 'TMap') || - (typename === 'TObjArray') || (typename === 'TClonesArray'); - }; - - /** @summary Adds specific methods to the object. - * - * JSROOT implements some basic methods for different ROOT classes. - * @param {object} obj - object where methods are assigned - * @param {string} typename - optional typename, if not specified, obj._typename will be used - * @private - */ - JSROOT.addMethods = function (obj, typename) { - this.extend(obj, JSROOT.getMethods(typename || obj._typename, obj)); - }; - - JSROOT.lastFFormat = ""; - - /** @summary Converts numeric value to string according to specified format. - * - * @param {number} value - value to convert - * @param {strting} fmt - format can be like 5.4g or 4.2e or 6.4f - * @returns {string} - converted value - * @private - */ - JSROOT.FFormat = function (value, fmt) { - if (!fmt) fmt = "6.4g"; - - JSROOT.lastFFormat = ""; - - fmt = fmt.trim(); - var len = fmt.length; - if (len < 2) return value.toFixed(4); - var last = fmt[len - 1]; - fmt = fmt.slice(0, len - 1); - var isexp = null; - var prec = fmt.indexOf("."); - if (prec < 0) prec = 4; else prec = Number(fmt.slice(prec + 1)); - if (isNaN(prec) || (prec < 0) || (prec == null)) prec = 4; - - var significance = false; - if ((last === 'e') || (last === 'E')) { - isexp = true; - } else if (last === 'Q') { - isexp = true; - significance = true; - } else if ((last === 'f') || (last === 'F')) { - isexp = false; - } else if (last === 'W') { - isexp = false; - significance = true; - } else if ((last === 'g') || (last === 'G')) { - var se = JSROOT.FFormat(value, fmt + 'Q'), - _fmt = JSROOT.lastFFormat, - sg = JSROOT.FFormat(value, fmt + 'W'); - - if (se.length < sg.length) { - JSROOT.lastFFormat = _fmt; - return se; - } - return sg; - } else { - isexp = false; - prec = 4; - } - - if (isexp) { - // for exponential representation only one significant digit befor point - if (significance) prec--; - if (prec < 0) prec = 0; - - JSROOT.lastFFormat = '5.' + prec + 'e'; - - return value.toExponential(prec); - } - - var sg = value.toFixed(prec); - - if (significance) { - - // when using fixed representation, one could get 0.0 - if ((value !== 0) && (Number(sg) === 0.) && (prec > 0)) { - prec = 20; - sg = value.toFixed(prec); - } - - var l = 0; - while ((l < sg.length) && (sg[l] === '0' || sg[l] === '-' || sg[l] === '.')) l++; - - var diff = sg.length - l - prec; - if (sg.indexOf(".") > l) diff--; - - if (diff !== 0) { - prec -= diff; - if (prec < 0) prec = 0; else if (prec > 20) prec = 20; - sg = value.toFixed(prec); - } - } - - JSROOT.lastFFormat = '5.' + prec + 'f'; - - return sg; - }; - - /** @summary Implements log10 - * @private */ - JSROOT.log10 = function (n) { - return Math.log(n) / Math.log(10); - }; - - // Dummy function, will be redefined when JSRootPainter is loaded - JSROOT.progress = function (msg, tmout) { - if ((msg !== undefined) && (typeof msg == "string")) JSROOT.console(msg); - }; - - // connect to the TWebWindow instance - JSROOT.ConnectWebWindow = function (arg) { - if (typeof arg == 'function') arg = {callback: arg}; - - if (arg.openui5src) JSROOT.openui5src = arg.openui5src; - if (arg.openui5libs) JSROOT.openui5libs = arg.openui5libs; - JSROOT.AssertPrerequisites("2d;" + (arg && arg.prereq ? arg.prereq : ""), function () { - if (arg && arg.prereq) delete arg.prereq; - JSROOT.ConnectWebWindow(arg); - }, (arg ? arg.prereq_logdiv : undefined)); - }; - - /** Initialize JSROOT. - * Called when script is loaded. Process URL parameters, supplied with JSRootCore.js script - * @private - */ - JSROOT.Initialize = function () { - - if (JSROOT.source_fullpath.length === 0) return this; - - function window_on_load(func) { - if (func != null) { - if (document.attachEvent ? document.readyState === 'complete' : document.readyState !== 'loading') - func(); - else - window.onload = func; - } - return JSROOT; - } - - var src = JSROOT.source_fullpath; - - if (JSROOT.GetUrlOption('nocache', src) != null) JSROOT.nocache = (new Date).getTime(); // use timestamp to overcome cache limitation - - if (JSROOT.GetUrlOption('gui', src) !== null) - return window_on_load(function () { - JSROOT.BuildSimpleGUI(); - }); - - if (typeof define === "function" && define.amd) - return window_on_load(function () { - JSROOT.BuildSimpleGUI('check_existing_elements'); - }); - - var prereq = ""; - if (JSROOT.GetUrlOption('io', src) != null) prereq += "io;"; - if (JSROOT.GetUrlOption('tree', src) != null) prereq += "tree;"; - if (JSROOT.GetUrlOption('2d', src) != null) prereq += "2d;"; - if (JSROOT.GetUrlOption('v7', src) != null) prereq += "v7;"; - if (JSROOT.GetUrlOption('hist', src) != null) prereq += "2d;hist;"; - if (JSROOT.GetUrlOption('hierarchy', src) != null) prereq += "2d;hierarchy;"; - if (JSROOT.GetUrlOption('jq2d', src) != null) prereq += "2d;hierarchy;jq2d;"; - if (JSROOT.GetUrlOption('more2d', src) != null) prereq += "more2d;"; - if (JSROOT.GetUrlOption('geom', src) != null) prereq += "geom;"; - if (JSROOT.GetUrlOption('3d', src) != null) prereq += "3d;"; - if (JSROOT.GetUrlOption('math', src) != null) prereq += "math;"; - if (JSROOT.GetUrlOption('mathjax', src) != null) prereq += "mathjax;"; - if (JSROOT.GetUrlOption('openui5', src) != null) prereq += "openui5;"; - var user = JSROOT.GetUrlOption('load', src), - onload = JSROOT.GetUrlOption('onload', src), - bower = JSROOT.GetUrlOption('bower', src); - - if (user) prereq += "io;2d;load:" + user; - if ((bower === null) && (JSROOT.source_dir.indexOf("bower_components/jsroot/") >= 0)) bower = ""; - if (bower !== null) { - if (bower.length > 0) JSROOT.bower_dir = bower; else if (JSROOT.source_dir.indexOf("jsroot/") === JSROOT.source_dir.length - 7) - JSROOT.bower_dir = JSROOT.source_dir.substr(0, JSROOT.source_dir.length - 7); - if (JSROOT.bower_dir !== null) console.log("Set JSROOT.bower_dir to " + JSROOT.bower_dir); - } - - if ((prereq.length > 0) || (onload != null)) - window_on_load(function () { - if (prereq.length > 0) JSROOT.AssertPrerequisites(prereq, onload); else if (onload != null) { - onload = JSROOT.findFunction(onload); - if (typeof onload == 'function') onload(); - } - }); - - return this; - }; - - return JSROOT; - -})); \ No newline at end of file diff --git a/dataforge-vis-spatial-js/src/main/resources/JSRootCore.js_mod b/dataforge-vis-spatial-js/src/main/resources/JSRootCore.js_mod new file mode 100644 index 00000000..ccef878d --- /dev/null +++ b/dataforge-vis-spatial-js/src/main/resources/JSRootCore.js_mod @@ -0,0 +1,1886 @@ +const System = {}; + +System.version = "dev 25/03/2019"; + +System.source_dir = ""; +System.source_min = false; +System.source_fullpath = ""; // full name of source script +System.bower_dir = null; // when specified, use standard libs from bower location +System.nocache = false; +System.sources = ['core']; // indicates which major sources were loaded + +System.id_counter = 0; + +//openuicfg // DO NOT DELETE, used to configure openui5 usage like openui5src = "nojsroot"; + +// use_full_libs = true; + +System.touches = false; +System.browser = {isOpera: false, isFirefox: true, isSafari: false, isChrome: false, isIE: false, isWin: false}; + + +/** Initialize + * Called when script is loaded. Process URL parameters, supplied with JSRootCore.js script + * @private + */ +System.Initialize = function() { + + if (BatchMode === undefined) + System.BatchMode = false; // when true, disables all kind of interactive features + + if ((typeof document !== "undefined") && (typeof window !== "undefined")) { + let scripts = document.getElementsByTagName('script'); + for (var n = 0; n < scripts.length; ++n) { + if (!scripts[n].src || (typeof scripts[n].src !== 'string')) continue; + + var pos = scripts[n].src.indexOf("scripts/JSRootCore."); + if (pos < 0) continue; + + System.source_dir = scripts[n].src.substr(0, pos); + System.source_min = scripts[n].src.indexOf("scripts/JSRootCore.min.js") >= 0; + System.source_fullpath = scripts[n].src; + + if ((console !== undefined) && (typeof console.log == 'function')) + console.log("Set source_dir to " + source_dir + ", " + version); + break; + } + + System.touches = ('ontouchend' in document); // identify if touch events are supported + browser.isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; + browser.isFirefox = typeof InstallTrigger !== 'undefined'; + browser.isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; + browser.isChrome = !!window.chrome && !browser.isOpera; + browser.isIE = !!document.documentMode; + browser.isWin = navigator.platform.indexOf('Win') >= 0; + browser.isChromeHeadless = navigator.userAgent.indexOf('HeadlessChrome') >= 0; + } + + System.browser.isWebKit = browser.isChrome || browser.isSafari || browser.isOpera; + + if (source_fullpath.length === 0) return System; + + function window_on_load(func) { + if (func != null) { + if (document.attachEvent ? document.readyState === 'complete' : document.readyState !== 'loading') + func(); + else + window.onload = func; + } + return System; + } + + var src = source_fullpath; + + if (GetUrlOption('nocache', src) != null) System.nocache = (new Date).getTime(); // use timestamp to overcome cache limitation + + if (GetUrlOption('gui', src) !== null) + return window_on_load(function () { + BuildSimpleGUI(); + }); + + if (typeof define === "function" && define.amd) + return window_on_load(function () { + BuildSimpleGUI('check_existing_elements'); + }); + + var prereq = ""; + if (GetUrlOption('io', src) != null) prereq += "io;"; + if (GetUrlOption('tree', src) != null) prereq += "tree;"; + if (GetUrlOption('2d', src) != null) prereq += "2d;"; + if (GetUrlOption('v7', src) != null) prereq += "v7;"; + if (GetUrlOption('hist', src) != null) prereq += "2d;hist;"; + if (GetUrlOption('hierarchy', src) != null) prereq += "2d;hierarchy;"; + if (GetUrlOption('jq2d', src) != null) prereq += "2d;hierarchy;jq2d;"; + if (GetUrlOption('more2d', src) != null) prereq += "more2d;"; + if (GetUrlOption('geom', src) != null) prereq += "geom;"; + if (GetUrlOption('3d', src) != null) prereq += "3d;"; + if (GetUrlOption('math', src) != null) prereq += "math;"; + if (GetUrlOption('mathjax', src) != null) prereq += "mathjax;"; + if (GetUrlOption('openui5', src) != null) prereq += "openui5;"; + var user = GetUrlOption('load', src), + onload = GetUrlOption('onload', src), + bower = GetUrlOption('bower', src); + + if (user) prereq += "io;2d;load:" + user; + if ((bower === null) && (source_dir.indexOf("bower_components/jsroot/") >= 0)) bower = ""; + if (bower !== null) { + if (bower.length > 0) System.bower_dir = bower; else if (source_dir.indexOf("jsroot/") === source_dir.length - 7) + System.bower_dir = source_dir.substr(0, source_dir.length - 7); + if (System.bower_dir !== null) console.log("Set bower_dir to " + bower_dir); + } + + if ((prereq.length > 0) || (onload != null)) + window_on_load(function () { + if (prereq.length > 0) AssertPrerequisites(prereq, onload); else if (onload != null) { + onload = System.findFunction(onload); + if (typeof onload == 'function') onload(); + } + }); + + return System; +}; + + +// connect to the TWebWindow instance +System.ConnectWebWindow = arg => { + if (typeof arg == 'function') arg = {callback: arg}; + + if (arg.openui5src) this.openui5src = arg.openui5src; + if (arg.openui5libs) this.openui5libs = arg.openui5libs; + AssertPrerequisites("2d;" + (arg && arg.prereq ? arg.prereq : ""), function () { + if (arg && arg.prereq) delete arg.prereq; + ConnectWebWindow(arg); + }, (arg ? arg.prereq_logdiv : undefined)); +}; + +// default draw styles, can be changed after loading of JSRootCore.js +// this style also can be changed providing style=itemname in the URL +const gStyle = { + Tooltip: 1, // 0 - off, 1 - on + TooltipAnimation: 500, // time in msec for appearance of tooltips, 0 - no animation + ContextMenu: true, + Zooming: true, // global zooming flag, enable/disable any kind of interactive zooming + ZoomMouse: true, // Zooming with the mouse events + ZoomWheel: true, // Zooming with mouse wheel + ZoomTouch: true, // Zooming with the touch devices + MoveResize: true, // enable move and resize of elements like statbox, title, pave, colz + DragAndDrop: true, // enables drag and drop functionality + ToolBar: 'popup', // show additional tool buttons on the canvas, false - disabled, true - enabled, 'popup' - only toggle button + ToolBarSide: 'left', // 'left' left-bottom corner on canvas, 'right' - right-bottom corner on canvas, opposite on sub-pads + ToolBarVert: false, // display tool bar vertical (default false) + CanEnlarge: true, // if drawing inside particular div can be enlarged on full window + CanAdjustFrame: false, // if frame position can be adjusted to let show axis or colz labels + ApproxTextSize: false, // calculation of text size consumes time and can be skipped to improve performance (but with side effects on text adjustments) + OptimizeDraw: 1, // drawing optimization: 0 - disabled, 1 - only for large (>5000 1d bins, >50 2d bins) histograms, 2 - always + AutoStat: true, + FrameNDC: {fX1NDC: 0.07, fY1NDC: 0.12, fX2NDC: 0.95, fY2NDC: 0.88}, + Palette: 57, + Latex: 2, // 0 - never, 1 - only latex symbols, 2 - normal TLatex processing (default), 3 - use MathJax for complex case, 4 - use MathJax always + // MathJax : 0, // depricated, will be supported till JSROOT 6.0, use Latex variable 0 - never, 1 - only for complex cases, 2 - always + ProgressBox: true, // show progress box + Embed3DinSVG: 2, // 0 - no embed, only 3D plot, 1 - overlay over SVG (IE/WebKit), 2 - embed into SVG (only Firefox) +// ImageSVG: !nodejs, // when producing SVG images, use elements to insert 3D drawings from three.js, + // To enable on nodejs, one should call "npm install canvas" + NoWebGL: false, // if true, WebGL will be disabled + GeoGradPerSegm: 6, // amount of grads per segment in TGeo spherical shapes like tube + GeoCompressComp: true, // if one should compress faces after creation of composite shape, + IgnoreUrlOptions: false, // if true, ignore all kind of URL options in the browser URL + HierarchyLimit: 250, // how many items shown on one level of hierarchy + SmallPad: {width: 150, height: 100}, // size of pad, where many features will be deactivated like text draw or zooming + + // XValuesFormat : "6.4g", // custom format for all X values + // YValuesFormat : "6.4g", // custom format for all Y values + // ZValuesFormat : "6.4g", // custom format for all Z values + + // these are TStyle attributes, which can be changed via URL 'style' parameter or delivered by TWebCanvas + + fOptLogx: 0, + fOptLogy: 0, + fOptLogz: 0, + fOptDate: 0, + fOptFile: 0, + fOptTitle: 1, + fPadBottomMargin: 0.1, + fPadTopMargin: 0.1, + fPadLeftMargin: 0.1, + fPadRightMargin: 0.1, + fPadGridX: false, + fPadGridY: false, + fPadTickX: 0, + fPadTickY: 0, + fStatColor: 0, + fStatTextColor: 1, + fStatBorderSize: 1, + fStatFont: 42, + fStatFontSize: 0, + fStatStyle: 1001, + fStatFormat: "6.4g", + fStatX: 0.98, + fStatY: 0.935, + fStatW: 0.2, + fStatH: 0.16, + fTitleAlign: 23, + fTitleColor: 0, + fTitleTextColor: 1, + fTitleBorderSize: 0, + fTitleFont: 42, + fTitleFontSize: 0.05, + fTitleStyle: 0, + fTitleX: 0.5, + fTitleY: 0.995, + fTitleW: 0, + fTitleH: 0, + fFitFormat: "5.4g", + fOptStat: 1111, + fOptFit: 0, + fNumberContours: 20, + fGridColor: 0, + fGridStyle: 3, + fGridWidth: 1, + fFrameFillColor: 0, + fFrameFillStyle: 1001, + fFrameLineColor: 1, + fFrameLineWidth: 1, + fFrameLineStyle: 1, + fFrameBorderSize: 1, + fFrameBorderMode: 0, + fEndErrorSize: 2, // size in pixels of end error for E1 draw options + fErrorX: 0.5, // X size of the error marks for the histogram drawings + fHistMinimumZero: false, // when true, BAR and LEGO drawing using base = 0 + fPaintTextFormat: "g", + fTimeOffset: 788918400 // UTC time at 01/01/95 +}; + +/** Generate mask for given bit + * + * @param {number} n bit number + * @returns {Number} produced make + * @private */ +export function BIT(n) { + return 1 << (n); +} + +/** TH1 status bits + * @private */ +const TH1StatusBits = { + kNoStats: BIT(9), // don't draw stats box + kUserContour: BIT(10), // user specified contour levels + kCanRebin: BIT(11), // can rebin axis + kLogX: BIT(15), // X-axis in log scale + kIsZoomed: BIT(16), // bit set when zooming on Y axis + kNoTitle: BIT(17), // don't draw the histogram title + kIsAverage: BIT(18) // Bin contents are average (used by Add) +}; + + +/** Wrapper for console.log, let redirect output to specified div element + * @private */ +function console(value, divid) { + if ((typeof divid == 'string') && document.getElementById(divid)) + document.getElementById(divid).innerHTML = value; + else if ((typeof console != 'undefined') && (typeof console.log == 'function')) + console.log(value); +} + +/** @summary Wrapper for alert, throws Error in Node.js + * @private */ +alert = function (msg) { + if (this.nodeis) throw new Error(msg); + if (typeof alert === 'function') alert(msg); + else console('ALERT: ' + msg); +}; + + +/** + * @summary Seed simple random generator + * + * @private + * @param {number} i seed value + */ +export function seed(i) { + i = Math.abs(i); + if (i > 1e8) i = Math.abs(1e8 * Math.sin(i)); else if (i < 1) i *= 1e8; + this.m_w = Math.round(i); + this.m_z = 987654321; +} + +/** + * @summary Simple random generator + * + * @desc Works like Math.random(), but with configurable seed - see {@link seed} + * @private + * @returns {number} random value between 0 (inclusive) and 1.0 (exclusive) + */ +export function random() { + if (this.m_z === undefined) return Math.random(); + this.m_z = (36969 * (this.m_z & 65535) + (this.m_z >> 16)) & 0xffffffff; + this.m_w = (18000 * (this.m_w & 65535) + (this.m_w >> 16)) & 0xffffffff; + var result = ((this.m_z << 16) + this.m_w) & 0xffffffff; + result /= 4294967296; + return result + 0.5; +} + +/** @summary Should be used to reintroduce objects references, produced by TBufferJSON. + * + * @desc Replace all references inside object, object should not be null + * Idea of the code taken from JSON-R code, found on + * https://github.com/graniteds/jsonr + * Only unref part was used, arrays are not accounted as objects + * @param {object} obj object where references will be replaced + * @returns {object} same object with replaced references + * @private */ +function JSONR_unref(obj) { + + var map = [], newfmt = undefined; + + function unref_value(value) { + if ((value === null) || (value === undefined)) return; + + if (typeof value === 'string') { + if (newfmt || (value.length < 6) || (value.indexOf("$ref:") !== 0)) return; + var ref = parseInt(value.substr(5)); + if (isNaN(ref) || (ref < 0) || (ref >= map.length)) return; + newfmt = false; + return map[ref]; + } + + if (typeof value !== 'object') return; + + var i, k, res, proto = Object.prototype.toString.apply(value); + + // scan array - it can contain other objects + if ((proto.indexOf('[object') === 0) && (proto.indexOf('Array]') > 0)) { + for (i = 0; i < value.length; ++i) { + res = unref_value(value[i]); + if (res !== undefined) value[i] = res; + } + return; + } + + var ks = Object.keys(value), len = ks.length; + + if ((newfmt !== false) && (len === 1) && (ks[0] === '$ref')) { + var ref = parseInt(value['$ref']); + if (isNaN(ref) || (ref < 0) || (ref >= map.length)) return; + newfmt = true; + return map[ref]; + } + + if ((newfmt !== false) && (len > 1) && (ks[0] === '$arr') && (ks[1] === 'len')) { + // this is ROOT-coded array + var arr = null, dflt = (value.$arr === "Bool") ? false : 0; + switch (value.$arr) { + case "Int8" : + arr = new Int8Array(value.len); + break; + case "Uint8" : + arr = new Uint8Array(value.len); + break; + case "Int16" : + arr = new Int16Array(value.len); + break; + case "Uint16" : + arr = new Uint16Array(value.len); + break; + case "Int32" : + arr = new Int32Array(value.len); + break; + case "Uint32" : + arr = new Uint32Array(value.len); + break; + case "Float32" : + arr = new Float32Array(value.len); + break; + case "Int64" : + case "Uint64" : + case "Float64" : + arr = new Float64Array(value.len); + break; + default : + arr = new Array(value.len); + break; + } + for (let k = 0; k < value.len; ++k) arr[k] = dflt; + + var nkey = 2, p = 0; + while (nkey < len) { + if (ks[nkey][0] === "p") p = value[ks[nkey++]]; // position + if (ks[nkey][0] !== 'v') throw new Error('Unexpected member ' + ks[nkey] + ' in array decoding'); + var v = value[ks[nkey++]]; // value + if (typeof v === 'object') { + for (let k = 0; k < v.length; ++k) arr[p++] = v[k]; + } else { + arr[p++] = v; + if ((nkey < len) && (ks[nkey][0] === 'n')) { + var cnt = value[ks[nkey++]]; // counter + while (--cnt) arr[p++] = v; + } + } + } + + return arr; + } + + if ((newfmt !== false) && (len === 3) && (ks[0] === '$pair') && (ks[1] === 'first') && (ks[2] === 'second')) { + newfmt = true; + var f1 = unref_value(value.first), + s1 = unref_value(value.second); + if (f1 !== undefined) value.first = f1; + if (s1 !== undefined) value.second = s1; + value._typename = value['$pair']; + delete value['$pair']; + return; // pair object is not counted in the objects map + } + + // debug code, can be commented out later + if (map.indexOf(value) >= 0) { + console('should never happen - object already in the map'); + return; + } + + // add object to object map + map.push(value); + + // add methods to all objects, where _typename is specified + if ('_typename' in value) System.addMethods(value); + + for (k = 0; k < len; ++k) { + i = ks[k]; + res = unref_value(value[i]); + if (res !== undefined) value[i] = res; + } + } + + unref_value(obj); + + return obj; +} + +/** @summary Just copies (not clone) all fields from source to the target object + * @desc This is simple replacement of jQuery.extend method + * @private */ +export function extend(tgt, src) { + if ((src === null) || (typeof src !== 'object')) return tgt; + if ((tgt === null) || (typeof tgt !== 'object')) tgt = {}; + + for (var k in src) + tgt[k] = src[k]; + + return tgt; +} + +/** @summary Make deep clone of the object, including all sub-objects + * @private */ +function clone(src, map, nofunc) { + if (src === null) return null; + + if (!map) { + map = {obj: [], clones: [], nofunc: nofunc}; + } else { + var i = map.obj.indexOf(src); + if (i >= 0) return map.clones[i]; + } + + var proto = Object.prototype.toString.apply(src); + + // process normal array + if (proto === '[object Array]') { + var tgt = []; + map.obj.push(src); + map.clones.push(tgt); + for (var i = 0; i < src.length; ++i) + if (typeof src[i] === 'object') + tgt.push(clone(src[i], map)); + else + tgt.push(src[i]); + + return tgt; + } + + // process typed array + if ((proto.indexOf('[object ') === 0) && (proto.indexOf('Array]') === proto.length - 6)) { + var tgt = []; + map.obj.push(src); + map.clones.push(tgt); + for (var i = 0; i < src.length; ++i) + tgt.push(src[i]); + + return tgt; + } + + var tgt = {}; + map.obj.push(src); + map.clones.push(tgt); + + for (var k in src) { + if (typeof src[k] === 'object') + tgt[k] = clone(src[k], map); + else if (!map.nofunc || (typeof src[k] !== 'function')) + tgt[k] = src[k]; + } + + return tgt; +} + +/** + * @summary Clear all functions from the contained objects + * + * Only such objects can be cloned when transfer to Worker or converted into JSON + * @param {object} src object where functions will be removed + * @returns {object} same object after all functions are removed + * @private + */ +function clear_func(src, map) { + if (src === null) return src; + + var proto = Object.prototype.toString.apply(src); + + if (proto === '[object Array]') { + for (var n = 0; n < src.length; n++) + if (typeof src[n] === 'object') + clear_func(src[n], map); + return src; + } + + if ((proto.indexOf('[object ') === 0) && (proto.indexOf('Array]') === proto.length - 6)) return src; + + if (!map) map = []; + var nomap = (map.length === 0); + if ('__clean_func__' in src) return src; + + map.push(src); + src['__clean_func__'] = true; + + for (var k in src) { + if (typeof src[k] === 'object') + clear_func(src[k], map); + else if (typeof src[k] === 'function') delete src[k]; + } + + if (nomap) + for (var n = 0; n < map.length; ++n) + delete map[n]['__clean_func__']; + + return src; +} + +/** + * @summary Parse JSON code produced with TBufferJSON. + * + * @param {string} json string to parse + * @return {object|null} returns parsed object + */ +export function parse(json) { + if (!json) return null; + let obj = JSON.parse(json); + if (obj) obj = JSONR_unref(obj); + return obj; +} + +/** + * @summary Parse multi.json request results + * @desc Method should be used to parse JSON code, produced by multi.json request of THttpServer + * + * @param {string} json string to parse + * @return {Array|null} returns array of parsed elements + */ +export function parse_multi(json) { + if (!json) return null; + var arr = JSON.parse(json); + if (arr && arr.length) + for (var i = 0; i < arr.length; ++i) + arr[i] = JSONR_unref(arr[i]); + return arr; +} + +/** + * @summary Method converts JavaScript object into ROOT-like JSON + * + * @desc Produced JSON can be used in parse() again + * When performed properly, JSON can be used in TBufferJSON to read data back with C++ + */ +export function toJSON(obj) { + if (!obj || typeof obj !== 'object') return ""; + + var map = []; // map of stored objects + + function copy_value(value) { + if (typeof value === "function") return undefined; + + if ((value === undefined) || (value === null) || (typeof value !== 'object')) return value; + + var proto = Object.prototype.toString.apply(value); + + // typed array need to be converted into normal array, otherwise looks strange + if ((proto.indexOf('[object ') === 0) && (proto.indexOf('Array]') === proto.length - 6)) { + var arr = new Array(value.length); + for (var i = 0; i < value.length; ++i) + arr[i] = copy_value(value[i]); + return arr; + } + + // this is how reference is code + var refid = map.indexOf(value); + if (refid >= 0) return {$ref: refid}; + + var ks = Object.keys(value), len = ks.length, tgt = {}; + + if ((len === 3) && (ks[0] === '$pair') && (ks[1] === 'first') && (ks[2] === 'second')) { + // special handling of pair objects which does not included into objects map + tgt.$pair = value.$pair; + tgt.first = copy_value(value.first); + tgt.second = copy_value(value.second); + return tgt; + } + + map.push(value); + + for (var k = 0; k < len; ++k) { + var name = ks[k]; + tgt[name] = copy_value(value[name]); + } + + return tgt; + } + + var tgt = copy_value(obj); + + return JSON.stringify(tgt); +} + +/** + * @summary Analyzes document.URL and extracts options after '?' mark + * + * @desc Following options supported ?opt1&opt2=3 + * In case of opt1 empty string will be returned, in case of opt2 '3' + * If option not found, null is returned (or default value value is provided) + * + * @param {string} opt option to search + * @param {string} full URL with options, document.URL will be used when not specified + * @returns {string|null} found value + * @private + */ +function GetUrlOption(opt, url, dflt) { + + if (dflt === undefined) dflt = null; + if ((opt === null) || (typeof opt != 'string') || (opt.length === 0)) return dflt; + + if (!url) { + if (gStyle.IgnoreUrlOptions || (typeof document === 'undefined')) return dflt; + url = document.URL; + } + + var pos = url.indexOf("?"), nquotes; + if (pos < 0) return dflt; + url = decodeURI(url.slice(pos + 1)); + + while (url.length > 0) { + + if (url === opt) return ""; + + // try to correctly handle quotes in the URL + pos = 0; + nquotes = 0; + while ((pos < url.length) && ((nquotes !== 0) || (url[pos] !== "&"))) { + switch (url[pos]) { + case "'": + if (nquotes >= 0) nquotes = (nquotes + 1) % 2; + break; + case '"': + if (nquotes <= 0) nquotes = (nquotes - 1) % 2; + break; + } + pos++; + } + + if (url.indexOf(opt) === 0) { + if (url[opt.length] === "&") return ""; + + if (url[opt.length] === "=") { + url = url.slice(opt.length + 1, pos); + if (((url[0] === "'") || (url[0] === '"')) && (url[0] === url[url.length - 1])) url = url.substr(1, url.length - 2); + return url; + } + } + + url = url.substr(pos + 1); + } + return dflt; +} + +/** + * @summary Parse string value as array. + * + * @desc It could be just simple string: "value" or + * array with or without string quotes: [element], ['elem1',elem2] + * + * @private + */ +function ParseAsArray(val) { + + var res = []; + + if (typeof val != 'string') return res; + + val = val.trim(); + if (val === "") return res; + + // return as array with single element + if ((val.length < 2) || (val[0] !== '[') || (val[val.length - 1] !== ']')) { + res.push(val); + return res; + } + + // try to split ourself, checking quotes and brackets + var nbr = 0, nquotes = 0, ndouble = 0, last = 1; + + for (var indx = 1; indx < val.length; ++indx) { + if (nquotes > 0) { + if (val[indx] === "'") nquotes--; + continue; + } + if (ndouble > 0) { + if (val[indx] === '"') ndouble--; + continue; + } + switch (val[indx]) { + case "'": + nquotes++; + break; + case '"': + ndouble++; + break; + case "[": + nbr++; + break; + case "]": + if (indx < val.length - 1) { + nbr--; + break; + } + case ",": + if (nbr === 0) { + var sub = val.substring(last, indx).trim(); + if ((sub.length > 1) && (sub[0] === sub[sub.length - 1]) && ((sub[0] === '"') || (sub[0] === "'"))) + sub = sub.substr(1, sub.length - 2); + res.push(sub); + last = indx + 1; + } + break; + } + } + + if (res.length === 0) + res.push(val.substr(1, val.length - 2).trim()); + + return res; +} + +/** + * @summary Special handling of URL options to produce array. + * + * @desc If normal option is specified ...?opt=abc, than array with single element will be created + * one could specify normal JSON array ...?opts=['item1','item2'] + * but also one could skip quotes ...?opts=[item1,item2] + * @private + */ +function GetUrlOptionAsArray(opt, url) { + + var res = []; + + while (opt.length > 0) { + var separ = opt.indexOf(";"); + var part = (separ > 0) ? opt.substr(0, separ) : opt; + + if (separ > 0) opt = opt.substr(separ + 1); else opt = ""; + + var canarray = true; + if (part[0] === '#') { + part = part.substr(1); + canarray = false; + } + + var val = GetUrlOption(part, url, null); + + if (canarray) res = res.concat(ParseAsArray(val)); + else if (val !== null) res.push(val); + } + return res; +} + +/** + * @summary Find function with given name. + * + * @desc Function name may include several namespaces like 'Painter.drawFrame' + * + * @private + */ +System.findFunction = function (name) { + if (typeof name === 'function') return name; + if (typeof name !== 'string') return null; + var names = name.split('.'), elem = null; + if (typeof window === 'object') elem = window; + if (names[0] === 'JSROOT') { + elem = this; + names.shift(); + } + + for (var n = 0; elem && (n < names.length); ++n) + elem = elem[names[n]]; + + return (typeof elem == 'function') ? elem : null; +}; + +/** + * @summary Generic method to invoke callback function. + * + * @param {object|function} func either normal function or container like + * { obj: object_pointer, func: name of method to call } + * @param arg1 first optional argument of callback + * @param arg2 second optional argument of callback + * + * @private + */ +System.CallBack = (func, arg1, arg2) => { + + if (typeof func == 'string') func = findFunction(func); + + if (!func) return; + + if (typeof func == 'function') return func(arg1, arg2); + + if (typeof func != 'object') return; + + if (('obj' in func) && ('func' in func) && + (typeof func.obj == 'object') && (typeof func.func == 'string') && + (typeof func.obj[func.func] == 'function')) { + return func.obj[func.func](arg1, arg2); + } +}; + + +// // function can be used to open ROOT file, I/O functionality will be loaded when missing +// function OpenFile(filename, callback) { +// AssertPrerequisites("io", function () { +// OpenFile(filename, callback); +// }); +// } + +// // function can be used to draw supported ROOT classes, +// // required functionality will be loaded automatically +// // if painter pointer required, one should load '2d' functionality itself +// // or use callback function which provides painter pointer as first argument +// // defined in JSRootPainter.js +// function draw(divid, obj, opt, callback) { +// AssertPrerequisites("2d", function () { +// draw(divid, obj, opt, callback); +// }); +// } +// +// // redraw object on given element +// // defined in JSRootPainter.js +// function redraw(divid, obj, opt, callback) { +// AssertPrerequisites("2d", function () { +// redraw(divid, obj, opt, callback); +// }); +// } +// +// // Create SVG, defined in JSRootPainter.js +// function MakeSVG(args, callback) { +// AssertPrerequisites("2d", function () { +// MakeSVG(args, callback); +// }); +// } + +/** @summary Method to build JSROOT GUI with browser + * @private + */ +function BuildSimpleGUI(user_scripts, andThen) { + if (typeof user_scripts == 'function') { + andThen = user_scripts; + user_scripts = null; + } + + var debugout = null, + nobrowser = GetUrlOption('nobrowser') != null, + requirements = "2d;hierarchy;", + simplegui = document.getElementById('simpleGUI'); + + //if (GetUrlOption('libs') !== null) use_full_libs = true; + + if (simplegui) { + debugout = 'simpleGUI'; + if (GetUrlOption('file') || GetUrlOption('files')) requirements += "io;"; + if (simplegui.getAttribute('nobrowser') && (simplegui.getAttribute('nobrowser') !== "false")) nobrowser = true; + } else if (document.getElementById('onlineGUI')) { + debugout = 'onlineGUI'; + } else if (document.getElementById('drawGUI')) { + debugout = 'drawGUI'; + nobrowser = true; + } else { + requirements += "io;"; + } + + if (user_scripts === 'check_existing_elements') { + user_scripts = null; + if (debugout == null) return; + } + + if (!nobrowser) requirements += 'jq2d;'; + + if (!user_scripts) user_scripts = GetUrlOption("autoload") || GetUrlOption("load"); + + if (user_scripts) requirements += "load:" + user_scripts + ";"; + + AssertPrerequisites(requirements, function () { + CallBack(findFunction(nobrowser ? 'BuildNobrowserGUI' : 'BuildGUI')); + CallBack(andThen); + }, debugout); +} + +/** @summary Create some ROOT classes + * + * @param {string} typename - ROOT class name + * @example + * var obj = Create("TNamed"); + * obj.fName = "name"; + * obj.fTitle = "title"; + */ +function create(typename, target) { + var obj = target || {}; + + switch (typename) { + case 'TObject': + extend(obj, {fUniqueID: 0, fBits: 0x3000008}); + break; + case 'TNamed': + extend(obj, {fUniqueID: 0, fBits: 0x3000008, fName: "", fTitle: ""}); + break; + case 'TList': + case 'THashList': + extend(obj, {name: typename, arr: [], opt: []}); + break; + case 'TAttAxis': + extend(obj, { + fNdivisions: 510, fAxisColor: 1, + fLabelColor: 1, fLabelFont: 42, fLabelOffset: 0.005, fLabelSize: 0.035, fTickLength: 0.03, + fTitleOffset: 1, fTitleSize: 0.035, fTitleColor: 1, fTitleFont: 42 + }); + break; + case 'TAxis': + create("TNamed", obj); + create("TAttAxis", obj); + extend(obj, { + fNbins: 0, fXmin: 0, fXmax: 0, fXbins: [], fFirst: 0, fLast: 0, + fBits2: 0, fTimeDisplay: false, fTimeFormat: "", fLabels: null, fModLabs: null + }); + break; + case 'TAttLine': + extend(obj, {fLineColor: 1, fLineStyle: 1, fLineWidth: 1}); + break; + case 'TAttFill': + extend(obj, {fFillColor: 0, fFillStyle: 0}); + break; + case 'TAttMarker': + extend(obj, {fMarkerColor: 1, fMarkerStyle: 1, fMarkerSize: 1.}); + break; + case 'TLine': + create("TObject", obj); + create("TAttLine", obj); + extend(obj, {fX1: 0, fX2: 1, fY1: 0, fY2: 1}); + break; + case 'TBox': + create("TObject", obj); + create("TAttLine", obj); + create("TAttFill", obj); + extend(obj, {fX1: 0, fX2: 1, fY1: 0, fY2: 1}); + break; + case 'TPave': + create("TBox", obj); + extend(obj, { + fX1NDC: 0., fY1NDC: 0, fX2NDC: 1, fY2NDC: 1, + fBorderSize: 0, fInit: 1, fShadowColor: 1, + fCornerRadius: 0, fOption: "blNDC", fName: "title" + }); + break; + case 'TAttText': + extend(obj, {fTextAngle: 0, fTextSize: 0, fTextAlign: 22, fTextColor: 1, fTextFont: 42}); + break; + case 'TPaveText': + create("TPave", obj); + create("TAttText", obj); + extend(obj, {fLabel: "", fLongest: 27, fMargin: 0.05, fLines: create("TList")}); + break; + case 'TPaveStats': + create("TPaveText", obj); + extend(obj, {fOptFit: 0, fOptStat: 0, fFitFormat: "", fStatFormat: "", fParent: null}); + break; + case 'TLegend': + create("TPave", obj); + create("TAttText", obj); + extend(obj, { + fColumnSeparation: 0, + fEntrySeparation: 0.1, + fMargin: 0.25, + fNColumns: 1, + fPrimitives: create("TList") + }); + break; + case 'TLegendEntry': + create("TObject", obj); + create("TAttText", obj); + create("TAttLine", obj); + create("TAttFill", obj); + create("TAttMarker", obj); + extend(obj, {fLabel: "", fObject: null, fOption: ""}); + break; + case 'TText': + create("TNamed", obj); + create("TAttText", obj); + extend(obj, {fLimitFactorSize: 3, fOriginSize: 0.04}); + break; + case 'TLatex': + create("TText", obj); + create("TAttLine", obj); + extend(obj, {fX: 0, fY: 0}); + break; + case 'TObjString': + create("TObject", obj); + extend(obj, {fString: ""}); + break; + case 'TH1': + create("TNamed", obj); + create("TAttLine", obj); + create("TAttFill", obj); + create("TAttMarker", obj); + + extend(obj, { + fNcells: 0, + fXaxis: create("TAxis"), + fYaxis: create("TAxis"), + fZaxis: create("TAxis"), + fBarOffset: 0, fBarWidth: 1000, fEntries: 0., + fTsumw: 0., fTsumw2: 0., fTsumwx: 0., fTsumwx2: 0., + fMaximum: -1111., fMinimum: -1111, fNormFactor: 0., fContour: [], + fSumw2: [], fOption: "", + fFunctions: create("TList"), + fBufferSize: 0, fBuffer: [], fBinStatErrOpt: 0, fStatOverflows: 2 + }); + break; + case 'TH1I': + case 'TH1F': + case 'TH1D': + case 'TH1S': + case 'TH1C': + create("TH1", obj); + obj.fArray = []; + break; + case 'TH2': + create("TH1", obj); + extend(obj, {fScalefactor: 1., fTsumwy: 0., fTsumwy2: 0, fTsumwxy: 0}); + break; + case 'TH2I': + case 'TH2F': + case 'TH2D': + case 'TH2S': + case 'TH2C': + create("TH2", obj); + obj.fArray = []; + break; + case 'TH3': + create("TH1", obj); + extend(obj, { + fTsumwy: 0., + fTsumwy2: 0, + fTsumwz: 0., + fTsumwz2: 0, + fTsumwxy: 0, + fTsumwxz: 0, + fTsumwyz: 0 + }); + break; + case 'TH3I': + case 'TH3F': + case 'TH3D': + case 'TH3S': + case 'TH3C': + create("TH3", obj); + obj.fArray = []; + break; + case 'THStack': + create("TNamed", obj); + extend(obj, { + fHists: create("TList"), + fHistogram: null, + fMaximum: -1111, + fMinimum: -1111 + }); + break; + case 'TGraph': + create("TNamed", obj); + create("TAttLine", obj); + create("TAttFill", obj); + create("TAttMarker", obj); + extend(obj, { + fFunctions: create("TList"), fHistogram: null, + fMaxSize: 0, fMaximum: -1111, fMinimum: -1111, fNpoints: 0, fX: [], fY: [] + }); + break; + case 'TGraphAsymmErrors': + create("TGraph", obj); + extend(obj, {fEXlow: [], fEXhigh: [], fEYlow: [], fEYhigh: []}); + break; + case 'TMultiGraph': + create("TNamed", obj); + extend(obj, { + fFunctions: create("TList"), fGraphs: create("TList"), + fHistogram: null, fMaximum: -1111, fMinimum: -1111 + }); + break; + case 'TGraphPolargram': + create("TNamed", obj); + create("TAttText", obj); + create("TAttLine", obj); + extend(obj, { + fRadian: true, + fDegree: false, + fGrad: false, + fPolarLabelColor: 1, + fRadialLabelColor: 1, + fAxisAngle: 0, + fPolarOffset: 0.04, + fPolarTextSize: 0.04, + fRadialOffset: 0.025, + fRadialTextSize: 0.035, + fRwrmin: 0, + fRwrmax: 1, + fRwtmin: 0, + fRwtmax: 2 * Math.PI, + fTickpolarSize: 0.02, + fPolarLabelFont: 62, + fRadialLabelFont: 62, + fCutRadial: 0, + fNdivRad: 508, + fNdivPol: 508 + }); + break; + case 'TPolyLine': + create("TObject", obj); + create("TAttLine", obj); + create("TAttFill", obj); + extend(obj, {fLastPoint: -1, fN: 0, fOption: "", fX: null, fY: null}); + break; + case 'TGaxis': + create("TLine", obj); + create("TAttText", obj); + extend(obj, { + fChopt: "", fFunctionName: "", fGridLength: 0, + fLabelColor: 1, fLabelFont: 42, fLabelOffset: 0.005, fLabelSize: 0.035, + fName: "", fNdiv: 12, fTickSize: 0.02, fTimeFormat: "", + fTitle: "", fTitleOffset: 1, fTitleSize: 0.035, + fWmax: 100, fWmin: 0 + }); + break; + case 'TAttPad': + extend(obj, { + fLeftMargin: gStyle.fPadLeftMargin, + fRightMargin: gStyle.fPadRightMargin, + fBottomMargin: gStyle.fPadBottomMargin, + fTopMargin: gStyle.fPadTopMargin, + fXfile: 2, fYfile: 2, fAfile: 1, fXstat: 0.99, fYstat: 0.99, fAstat: 2, + fFrameFillColor: gStyle.fFrameFillColor, + fFrameFillStyle: gStyle.fFrameFillStyle, + fFrameLineColor: gStyle.fFrameLineColor, + fFrameLineWidth: gStyle.fFrameLineWidth, + fFrameLineStyle: gStyle.fFrameLineStyle, + fFrameBorderSize: gStyle.fFrameBorderSize, + fFrameBorderMode: gStyle.fFrameBorderMode + }); + break; + case 'TPad': + create("TObject", obj); + create("TAttLine", obj); + create("TAttFill", obj); + create("TAttPad", obj); + extend(obj, { + fX1: 0, + fY1: 0, + fX2: 1, + fY2: 1, + fXtoAbsPixelk: 1, + fXtoPixelk: 1, + fXtoPixel: 1, + fYtoAbsPixelk: 1, + fYtoPixelk: 1, + fYtoPixel: 1, + fUtoAbsPixelk: 1, + fUtoPixelk: 1, + fUtoPixel: 1, + fVtoAbsPixelk: 1, + fVtoPixelk: 1, + fVtoPixel: 1, + fAbsPixeltoXk: 1, + fPixeltoXk: 1, + fPixeltoX: 1, + fAbsPixeltoYk: 1, + fPixeltoYk: 1, + fPixeltoY: 1, + fXlowNDC: 0, + fYlowNDC: 0, + fXUpNDC: 0, + fYUpNDC: 0, + fWNDC: 1, + fHNDC: 1, + fAbsXlowNDC: 0, + fAbsYlowNDC: 0, + fAbsWNDC: 1, + fAbsHNDC: 1, + fUxmin: 0, + fUymin: 0, + fUxmax: 0, + fUymax: 0, + fTheta: 30, + fPhi: 30, + fAspectRatio: 0, + fNumber: 0, + fLogx: gStyle.fOptLogx, + fLogy: gStyle.fOptLogy, + fLogz: gStyle.fOptLogz, + fTickx: gStyle.fPadTickX, + fTicky: gStyle.fPadTickY, + fPadPaint: 0, + fCrosshair: 0, + fCrosshairPos: 0, + fBorderSize: 2, + fBorderMode: 0, + fModified: false, + fGridx: gStyle.fPadGridX, + fGridy: gStyle.fPadGridY, + fAbsCoord: false, + fEditable: true, + fFixedAspectRatio: false, + fPrimitives: create("TList"), + fExecs: null, + fName: "pad", + fTitle: "canvas" + }); + + break; + case 'TAttCanvas': + extend(obj, { + fXBetween: 2, fYBetween: 2, fTitleFromTop: 1.2, + fXdate: 0.2, fYdate: 0.3, fAdate: 1 + }); + break; + case 'TCanvas': + create("TPad", obj); + extend(obj, { + fNumPaletteColor: 0, fNextPaletteColor: 0, fDISPLAY: "$DISPLAY", + fDoubleBuffer: 0, fRetained: true, fXsizeUser: 0, + fYsizeUser: 0, fXsizeReal: 20, fYsizeReal: 10, + fWindowTopX: 0, fWindowTopY: 0, fWindowWidth: 0, fWindowHeight: 0, + fCw: 500, fCh: 300, fCatt: create("TAttCanvas"), + kMoveOpaque: true, kResizeOpaque: true, fHighLightColor: 5, + fBatch: true, kShowEventStatus: false, kAutoExec: true, kMenuBar: true + }); + break; + case 'TGeoVolume': + create("TNamed", obj); + create("TAttLine", obj); + create("TAttFill", obj); + extend(obj, { + fGeoAtt: 0, + fFinder: null, + fMedium: null, + fNodes: null, + fNtotal: 0, + fNumber: 0, + fRefCount: 0, + fShape: null, + fVoxels: null + }); + break; + case 'TGeoNode': + create("TNamed", obj); + extend(obj, {fGeoAtt: 0, fMother: null, fNovlp: 0, fNumber: 0, fOverlaps: null, fVolume: null}); + break; + case 'TGeoNodeMatrix': + create("TGeoNode", obj); + extend(obj, {fMatrix: null}); + break; + case 'TGeoTrack': + create("TObject", obj); + create("TAttLine", obj); + create("TAttMarker", obj); + extend(obj, {fGeoAtt: 0, fNpoints: 0, fPoints: []}); + break; + } + + obj._typename = typename; + addMethods(obj); + return obj; +} + +/** @summary Create histogram object + * @param {string} typename - histogram typename like TH1I or TH2F + * @param {number} nbinsx - number of bins on X-axis + * @param {number} [nbinsy] - number of bins on Y-axis (for 2D/3D histograms) + * @param {number} [nbinsz] - number of bins on Z-axis (for 3D histograms) + */ +function CreateHistogram(typename, nbinsx, nbinsy, nbinsz) { + // create histogram object of specified type + // if bins numbers are specified, appropriate typed array will be created + var histo = create(typename); + if (!histo.fXaxis || !histo.fYaxis || !histo.fZaxis) return null; + histo.fName = "hist"; + histo.fTitle = "title"; + if (nbinsx) extend(histo.fXaxis, {fNbins: nbinsx, fXmin: 0, fXmax: nbinsx}); + if (nbinsy) extend(histo.fYaxis, {fNbins: nbinsy, fXmin: 0, fXmax: nbinsy}); + if (nbinsz) extend(histo.fZaxis, {fNbins: nbinsz, fXmin: 0, fXmax: nbinsz}); + switch (parseInt(typename[2])) { + case 1: + if (nbinsx) histo.fNcells = nbinsx + 2; + break; + case 2: + if (nbinsx && nbinsy) histo.fNcells = (nbinsx + 2) * (nbinsy + 2); + break; + case 3: + if (nbinsx && nbinsy && nbinsz) histo.fNcells = (nbinsx + 2) * (nbinsy + 2) * (nbinsz + 2); + break; + } + if (histo.fNcells > 0) { + switch (typename[3]) { + case "C" : + histo.fArray = new Int8Array(histo.fNcells); + break; + case "S" : + histo.fArray = new Int16Array(histo.fNcells); + break; + case "I" : + histo.fArray = new Int32Array(histo.fNcells); + break; + case "F" : + histo.fArray = new Float32Array(histo.fNcells); + break; + case "L" : + histo.fArray = new Float64Array(histo.fNcells); + break; + case "D" : + histo.fArray = new Float64Array(histo.fNcells); + break; + default: + histo.fArray = new Array(histo.fNcells); + break; + } + for (var i = 0; i < histo.fNcells; ++i) histo.fArray[i] = 0; + } + return histo; +} + +/** @summary Creates TPolyLine object + * @param {number} npoints - number of points + * @param {boolean} [use_int32] - use Int32Array type for points, default is Float32Array */ +function CreateTPolyLine(npoints, use_int32) { + var poly = create("TPolyLine"); + if (npoints) { + poly.fN = npoints; + if (use_int32) { + poly.fX = new Int32Array(npoints); + poly.fY = new Int32Array(npoints); + } else { + poly.fX = new Float32Array(npoints); + poly.fY = new Float32Array(npoints); + } + } + + return poly; +} + +/** @summary Creates TGraph object + * @param {number} npoints - number of points in TGraph + * @param {array} [xpts] - array with X coordinates + * @param {array} [ypts] - array with Y coordinates */ +function CreateTGraph(npoints, xpts, ypts) { + let graph = extend(create("TGraph"), {fBits: 0x3000408, fName: "graph", fTitle: "title"}); + + if (npoints > 0) { + graph.fMaxSize = graph.fNpoints = npoints; + + let usex = (typeof xpts == 'object') && (xpts.length === npoints); + let usey = (typeof ypts == 'object') && (ypts.length === npoints); + + for (var i = 0; i < npoints; ++i) { + graph.fX.push(usex ? xpts[i] : i / npoints); + graph.fY.push(usey ? ypts[i] : i / npoints); + } + } + + return graph; +} + +/** @summary Creates THStack object + * @desc + * As arguments one could specify any number of histograms objects + * @example + * var nbinsx = 20; + * var h1 = CreateHistogram("TH1F", nbinsx); + * var h2 = CreateHistogram("TH1F", nbinsx); + * var h3 = CreateHistogram("TH1F", nbinsx); + * var stack = CreateTHStack(h1, h2, h3); + * */ +function CreateTHStack() { + var stack = create("THStack"); + for (var i = 0; i < arguments.length; ++i) + stack.fHists.Add(arguments[i], ""); + return stack; +} + +/** @summary Creates TMultiGraph object + * @desc + * As arguments one could specify any number of TGraph objects */ +function CreateTMultiGraph() { + var mgraph = create("TMultiGraph"); + for (var i = 0; i < arguments.length; ++i) + mgraph.fGraphs.Add(arguments[i], ""); + return mgraph; +} + +System.methodsCache = {}; // variable used to keep methods for known classes + +/** @summary Returns methods for given typename + * @private + */ +System.getMethods = function (typename, obj) { + + let m = System.methodsCache[typename], + has_methods = (m !== undefined); + + if (!has_methods) m = {}; + + // Due to binary I/O such TObject methods may not be set for derived classes + // Therefore when methods requested for given object, check also that basic methods are there + if ((typename === "TObject") || (typename === "TNamed") || (obj && (obj.fBits !== undefined))) + if (m.TestBit === undefined) { + m.TestBit = function (f) { + return (this.fBits & f) !== 0; + }; + m.InvertBit = function (f) { + this.fBits = this.fBits ^ (f & 0xffffff); + }; + } + + if (has_methods) return m; + + if ((typename === 'TList') || (typename === 'THashList')) { + m.Clear = function () { + this.arr = []; + this.opt = []; + }; + m.Add = function (obj, opt) { + this.arr.push(obj); + this.opt.push((opt && typeof opt == 'string') ? opt : ""); + }; + m.AddFirst = function (obj, opt) { + this.arr.unshift(obj); + this.opt.unshift((opt && typeof opt == 'string') ? opt : ""); + }; + m.RemoveAt = function (indx) { + this.arr.splice(indx, 1); + this.opt.splice(indx, 1); + } + } + + if ((typename === "TPaveText") || (typename === "TPaveStats")) { + m.AddText = function (txt) { + // this.fLines.Add({ _typename: 'TLatex', fTitle: txt, fTextColor: 1 }); + var line = create("TLatex"); + line.fTitle = txt; + this.fLines.Add(line); + }; + m.Clear = function () { + this.fLines.Clear(); + } + } + + if ((typename.indexOf("TF1") === 0) || (typename === "TF2")) { + m.addFormula = function (obj) { + if (!obj) return; + if (this.formulas === undefined) this.formulas = []; + this.formulas.push(obj); + }; + + m.evalPar = function (x, y) { + if (!('_func' in this) || (this._title !== this.fTitle)) { + + var _func = this.fTitle, isformula = false, pprefix = "["; + if (_func === "gaus") _func = "gaus(0)"; + if (this.fFormula && typeof this.fFormula.fFormula == "string") { + if (this.fFormula.fFormula.indexOf("[](double*x,double*p)") === 0) { + isformula = true; + pprefix = "p["; + _func = this.fFormula.fFormula.substr(21); + } else { + _func = this.fFormula.fFormula; + pprefix = "[p"; + } + if (this.fFormula.fClingParameters && this.fFormula.fParams) { + for (var i = 0; i < this.fFormula.fParams.length; ++i) { + var regex = new RegExp('(\\[' + this.fFormula.fParams[i].first + '\\])', 'g'), + parvalue = this.fFormula.fClingParameters[this.fFormula.fParams[i].second]; + _func = _func.replace(regex, (parvalue < 0) ? "(" + parvalue + ")" : parvalue); + } + } + } + + if ('formulas' in this) + for (var i = 0; i < this.formulas.length; ++i) + while (_func.indexOf(this.formulas[i].fName) >= 0) + _func = _func.replace(this.formulas[i].fName, this.formulas[i].fTitle); + _func = _func.replace(/\b(abs)\b/g, 'TMath::Abs') + .replace(/TMath::Exp\(/g, 'Math.exp(') + .replace(/TMath::Abs\(/g, 'Math.abs('); + if (typeof Math == 'object') { + this._math = Math; + _func = _func.replace(/TMath::Prob\(/g, 'this._math.Prob(') + .replace(/TMath::Gaus\(/g, 'this._math.Gaus(') + .replace(/TMath::BreitWigner\(/g, 'this._math.BreitWigner(') + .replace(/xygaus\(/g, 'this._math.gausxy(this, x, y, ') + .replace(/gaus\(/g, 'this._math.gaus(this, x, ') + .replace(/gausn\(/g, 'this._math.gausn(this, x, ') + .replace(/expo\(/g, 'this._math.expo(this, x, ') + .replace(/landau\(/g, 'this._math.landau(this, x, ') + .replace(/landaun\(/g, 'this._math.landaun(this, x, ') + .replace(/ROOT::Math::/g, 'this._math.'); + } + for (var i = 0; i < this.fNpar; ++i) { + var parname = pprefix + i + "]"; + while (_func.indexOf(parname) !== -1) + _func = _func.replace(parname, '(' + this.GetParValue(i) + ')'); + } + _func = _func.replace(/\b(sin)\b/gi, 'Math.sin') + .replace(/\b(cos)\b/gi, 'Math.cos') + .replace(/\b(tan)\b/gi, 'Math.tan') + .replace(/\b(exp)\b/gi, 'Math.exp') + .replace(/\b(pow)\b/gi, 'Math.pow') + .replace(/pi/g, 'Math.PI'); + for (var n = 2; n < 10; ++n) + _func = _func.replace('x^' + n, 'Math.pow(x,' + n + ')'); + + if (isformula) { + _func = _func.replace(/x\[0\]/g, "x"); + if (this._typename === "TF2") { + _func = _func.replace(/x\[1\]/g, "y"); + this._func = new Function("x", "y", _func).bind(this); + } else { + this._func = new Function("x", _func).bind(this); + } + } else if (this._typename === "TF2") + this._func = new Function("x", "y", "return " + _func).bind(this); + else + this._func = new Function("x", "return " + _func).bind(this); + + this._title = this.fTitle; + } + + return this._func(x, y); + }; + m.GetParName = function (n) { + if (this.fFormula && this.fFormula.fParams) return this.fFormula.fParams[n].first; + if (this.fNames && this.fNames[n]) return this.fNames[n]; + return "p" + n; + }; + m.GetParValue = function (n) { + if (this.fFormula && this.fFormula.fClingParameters) return this.fFormula.fClingParameters[n]; + if (this.fParams) return this.fParams[n]; + return undefined; + }; + m.GetParError = function (n) { + return this.fParErrors ? this.fParErrors[n] : undefined; + }; + m.GetNumPars = function () { + return this.fNpar; + } + } + + if (((typename.indexOf("TGraph") === 0) || (typename === "TCutG")) && (typename !== "TGraphPolargram") && (typename !== "TGraphTime")) { + // check if point inside figure specified by the TGraph + m.IsInside = function (xp, yp) { + var i, j = this.fNpoints - 1, x = this.fX, y = this.fY, oddNodes = false; + + for (i = 0; i < this.fNpoints; ++i) { + if ((y[i] < yp && y[j] >= yp) || (y[j] < yp && y[i] >= yp)) { + if (x[i] + (yp - y[i]) / (y[j] - y[i]) * (x[j] - x[i]) < xp) { + oddNodes = !oddNodes; + } + } + j = i; + } + + return oddNodes; + }; + } + + if (typename.indexOf("TH1") === 0 || + typename.indexOf("TH2") === 0 || + typename.indexOf("TH3") === 0) { + m.getBinError = function (bin) { + // -*-*-*-*-*Return value of error associated to bin number bin*-*-*-*-* + // if the sum of squares of weights has been defined (via Sumw2), + // this function returns the sqrt(sum of w2). + // otherwise it returns the sqrt(contents) for this bin. + if (bin >= this.fNcells) bin = this.fNcells - 1; + if (bin < 0) bin = 0; + if (bin < this.fSumw2.length) + return Math.sqrt(this.fSumw2[bin]); + return Math.sqrt(Math.abs(this.fArray[bin])); + }; + m.setBinContent = function (bin, content) { + // Set bin content - only trivial case, without expansion + this.fEntries++; + this.fTsumw = 0; + if ((bin >= 0) && (bin < this.fArray.length)) + this.fArray[bin] = content; + }; + } + + if (typename.indexOf("TH1") === 0) { + m.getBin = function (x) { + return x; + }; + m.getBinContent = function (bin) { + return this.fArray[bin]; + }; + m.Fill = function (x, weight) { + var axis = this.fXaxis, + bin = 1 + Math.floor((x - axis.fXmin) / (axis.fXmax - axis.fXmin) * axis.fNbins); + if (bin < 0) bin = 0; else if (bin > axis.fNbins + 1) bin = axis.fNbins + 1; + this.fArray[bin] += ((weight === undefined) ? 1 : weight); + } + } + + if (typename.indexOf("TH2") === 0) { + m.getBin = function (x, y) { + return (x + (this.fXaxis.fNbins + 2) * y); + }; + m.getBinContent = function (x, y) { + return this.fArray[this.getBin(x, y)]; + }; + m.Fill = function (x, y, weight) { + var axis1 = this.fXaxis, axis2 = this.fYaxis, + bin1 = 1 + Math.floor((x - axis1.fXmin) / (axis1.fXmax - axis1.fXmin) * axis1.fNbins), + bin2 = 1 + Math.floor((y - axis2.fXmin) / (axis2.fXmax - axis2.fXmin) * axis2.fNbins); + if (bin1 < 0) bin1 = 0; else if (bin1 > axis1.fNbins + 1) bin1 = axis1.fNbins + 1; + if (bin2 < 0) bin2 = 0; else if (bin2 > axis2.fNbins + 1) bin2 = axis2.fNbins + 1; + this.fArray[bin1 + (axis1.fNbins + 2) * bin2] += ((weight === undefined) ? 1 : weight); + } + } + + if (typename.indexOf("TH3") === 0) { + m.getBin = function (x, y, z) { + return (x + (this.fXaxis.fNbins + 2) * (y + (this.fYaxis.fNbins + 2) * z)); + }; + m.getBinContent = function (x, y, z) { + return this.fArray[this.getBin(x, y, z)]; + }; + m.Fill = function (x, y, z, weight) { + var axis1 = this.fXaxis, axis2 = this.fYaxis, axis3 = this.fZaxis, + bin1 = 1 + Math.floor((x - axis1.fXmin) / (axis1.fXmax - axis1.fXmin) * axis1.fNbins), + bin2 = 1 + Math.floor((y - axis2.fXmin) / (axis2.fXmax - axis2.fXmin) * axis2.fNbins), + bin3 = 1 + Math.floor((z - axis3.fXmin) / (axis3.fXmax - axis3.fXmin) * axis3.fNbins); + if (bin1 < 0) bin1 = 0; else if (bin1 > axis1.fNbins + 1) bin1 = axis1.fNbins + 1; + if (bin2 < 0) bin2 = 0; else if (bin2 > axis2.fNbins + 1) bin2 = axis2.fNbins + 1; + if (bin3 < 0) bin3 = 0; else if (bin3 > axis3.fNbins + 1) bin3 = axis3.fNbins + 1; + this.fArray[bin1 + (axis1.fNbins + 2) * (bin2 + (axis2.fNbins + 2) * bin3)] += ((weight === undefined) ? 1 : weight); + } + } + + if (typename.indexOf("TProfile") === 0) { + if (typename.indexOf("TProfile2D") === 0) { + m.getBin = function (x, y) { + return (x + (this.fXaxis.fNbins + 2) * y); + }; + m.getBinContent = function (x, y) { + var bin = this.getBin(x, y); + if (bin < 0 || bin >= this.fNcells) return 0; + if (this.fBinEntries[bin] < 1e-300) return 0; + if (!this.fArray) return 0; + return this.fArray[bin] / this.fBinEntries[bin]; + }; + m.getBinEntries = function (x, y) { + var bin = this.getBin(x, y); + if (bin < 0 || bin >= this.fNcells) return 0; + return this.fBinEntries[bin]; + } + } else { + m.getBin = function (x) { + return x; + }; + m.getBinContent = function (bin) { + if (bin < 0 || bin >= this.fNcells) return 0; + if (this.fBinEntries[bin] < 1e-300) return 0; + if (!this.fArray) return 0; + return this.fArray[bin] / this.fBinEntries[bin]; + }; + } + m.getBinEffectiveEntries = function (bin) { + if (bin < 0 || bin >= this.fNcells) return 0; + var sumOfWeights = this.fBinEntries[bin]; + if (!this.fBinSumw2 || this.fBinSumw2.length !== this.fNcells) { + // this can happen when reading an old file + return sumOfWeights; + } + var sumOfWeightsSquare = this.fBinSumw2[bin]; + return (sumOfWeightsSquare > 0) ? sumOfWeights * sumOfWeights / sumOfWeightsSquare : 0; + }; + m.getBinError = function (bin) { + if (bin < 0 || bin >= this.fNcells) return 0; + var cont = this.fArray[bin], // sum of bin w *y + sum = this.fBinEntries[bin], // sum of bin weights + err2 = this.fSumw2[bin], // sum of bin w * y^2 + neff = this.getBinEffectiveEntries(bin); // (sum of w)^2 / (sum of w^2) + if (sum < 1e-300) return 0; // for empty bins + var EErrorType = {kERRORMEAN: 0, kERRORSPREAD: 1, kERRORSPREADI: 2, kERRORSPREADG: 3}; + // case the values y are gaussian distributed y +/- sigma and w = 1/sigma^2 + if (this.fErrorMode === EErrorType.kERRORSPREADG) + return 1.0 / Math.sqrt(sum); + // compute variance in y (eprim2) and standard deviation in y (eprim) + var contsum = cont / sum, eprim = Math.sqrt(Math.abs(err2 / sum - contsum * contsum)); + if (this.fErrorMode === EErrorType.kERRORSPREADI) { + if (eprim !== 0) return eprim / Math.sqrt(neff); + // in case content y is an integer (so each my has an error +/- 1/sqrt(12) + // when the std(y) is zero + return 1.0 / Math.sqrt(12 * neff); + } + // if approximate compute the sums (of w, wy and wy2) using all the bins + // when the variance in y is zero + // case option "S" return standard deviation in y + if (this.fErrorMode === EErrorType.kERRORSPREAD) return eprim; + // default case : fErrorMode = kERRORMEAN + // return standard error on the mean of y + return (eprim / Math.sqrt(neff)); + }; + } + + if (typename === "TAxis") { + m.GetBinLowEdge = function (bin) { + if (this.fNbins <= 0) return 0; + if ((this.fXbins.length > 0) && (bin > 0) && (bin <= this.fNbins)) return this.fXbins[bin - 1]; + return this.fXmin + (bin - 1) * (this.fXmax - this.fXmin) / this.fNbins; + }; + m.GetBinCenter = function (bin) { + if (this.fNbins <= 0) return 0; + if ((this.fXbins.length > 0) && (bin > 0) && (bin < this.fNbins)) return (this.fXbins[bin - 1] + this.fXbins[bin]) / 2; + return this.fXmin + (bin - 0.5) * (this.fXmax - this.fXmin) / this.fNbins; + } + } + + if (typeof getMoreMethods == "function") + getMoreMethods(m, typename, obj); + + System.methodsCache[typename] = m; + return m; +}; + +/** @summary Returns true if object represents basic ROOT collections + * @private */ +function isRootCollection(lst, typename) { + if (lst && (typeof lst === 'object')) { + if ((lst.$kind === "TList") || (lst.$kind === "TObjArray")) return true; + if (!typename) typename = lst._typename; + } + if (!typename) return false; + return (typename === 'TList') || (typename === 'THashList') || (typename === 'TMap') || + (typename === 'TObjArray') || (typename === 'TClonesArray'); +} + +/** @summary Adds specific methods to the object. + * + * JSROOT implements some basic methods for different ROOT classes. + * @param {object} obj - object where methods are assigned + * @param {string} typename - optional typename, if not specified, obj._typename will be used + * @private + */ +System.addMethods = function (obj, typename) { + System.extend(obj, System.getMethods(typename || obj._typename, obj)); +}; + +let lastFFormat = ""; + +/** @summary Converts numeric value to string according to specified format. + * + * @param {number} value - value to convert + * @param {string} fmt - format can be like 5.4g or 4.2e or 6.4f + * @returns {string} - converted value + * @private + */ +function FFormat(value, fmt) { + if (!fmt) fmt = "6.4g"; + + lastFFormat = ""; + + fmt = fmt.trim(); + var len = fmt.length; + if (len < 2) return value.toFixed(4); + var last = fmt[len - 1]; + fmt = fmt.slice(0, len - 1); + var isexp = null; + var prec = fmt.indexOf("."); + if (prec < 0) prec = 4; else prec = Number(fmt.slice(prec + 1)); + if (isNaN(prec) || (prec < 0) || (prec == null)) prec = 4; + + var significance = false; + if ((last === 'e') || (last === 'E')) { + isexp = true; + } else if (last === 'Q') { + isexp = true; + significance = true; + } else if ((last === 'f') || (last === 'F')) { + isexp = false; + } else if (last === 'W') { + isexp = false; + significance = true; + } else if ((last === 'g') || (last === 'G')) { + var se = FFormat(value, fmt + 'Q'), + _fmt = lastFFormat, + sg = FFormat(value, fmt + 'W'); + + if (se.length < sg.length) { + lastFFormat = _fmt; + return se; + } + return sg; + } else { + isexp = false; + prec = 4; + } + + if (isexp) { + // for exponential representation only one significant digit befor point + if (significance) prec--; + if (prec < 0) prec = 0; + + lastFFormat = '5.' + prec + 'e'; + + return value.toExponential(prec); + } + + var sg = value.toFixed(prec); + + if (significance) { + + // when using fixed representation, one could get 0.0 + if ((value !== 0) && (Number(sg) === 0.) && (prec > 0)) { + prec = 20; + sg = value.toFixed(prec); + } + + var l = 0; + while ((l < sg.length) && (sg[l] === '0' || sg[l] === '-' || sg[l] === '.')) l++; + + var diff = sg.length - l - prec; + if (sg.indexOf(".") > l) diff--; + + if (diff !== 0) { + prec -= diff; + if (prec < 0) prec = 0; else if (prec > 20) prec = 20; + sg = value.toFixed(prec); + } + } + + lastFFormat = '5.' + prec + 'f'; + + return sg; +} + +/** @summary Implements log10 + * @private */ +function log10(n) { + return Math.log(n) / Math.log(10); +} + +// Dummy function, will be redefined when JSRootPainter is loaded +function progress(msg, tmout) { + if ((msg !== undefined) && (typeof msg == "string")) console(msg); +} + +export {System}; \ No newline at end of file diff --git a/dataforge-vis-spatial-js/src/main/resources/JSRootGeoBase.js b/dataforge-vis-spatial-js/src/main/resources/JSRootGeoBase.js index 60ca0718..b4d5724a 100644 --- a/dataforge-vis-spatial-js/src/main/resources/JSRootGeoBase.js +++ b/dataforge-vis-spatial-js/src/main/resources/JSRootGeoBase.js @@ -1,130 +1,141 @@ -/** @file JSRootGeoBase.js */ -/// Basic functions for work with TGeo classes +import * as JSROOT from "JSRootUtils" +import * as THREE from "three-full" +import * as ThreeBSP from "ThreeCSG" -(function (factory) { - if (typeof define === "function" && define.amd) { - define(['three-full', 'ThreeCSG', 'JSRootCore'], factory); - } else if (typeof exports === 'object' && typeof module !== 'undefined') { - factory(require("three-full"), require("./ThreeCSG.js"), require("./JSRootCore.js")); - } else { - if (typeof THREE == 'undefined') - throw new Error('THREE is not defined', 'JSRootGeoBase.js'); +/** @namespace GEO */ + /// Holder of all TGeo-related functions and classes +const GradPerSegm = 6; // grad per segment in cylinder/spherical symmetry shapes +const CompressComp = true; // use faces compression in composite shapes +const CompLimit = 20; // maximal number of components in composite shape - if (typeof ThreeBSP == 'undefined') - throw new Error('ThreeBSP is not defined', 'JSRootGeoBase.js'); - - if (typeof JSROOT == 'undefined') - throw new Error('JSROOT is not defined', 'JSRootGeoBase.js'); +let _warn_msgs = {}; - factory(THREE, ThreeBSP, JSROOT); - } -}(function (THREE, ThreeBSP, JSROOT) { +/** @memberOf GEO */ +const BITS = { + kVisOverride: JSROOT.BIT(0), // volume's vis. attributes are overwritten + kVisNone: JSROOT.BIT(1), // the volume/node is invisible, as well as daughters + kVisThis: JSROOT.BIT(2), // this volume/node is visible + kVisDaughters: JSROOT.BIT(3), // all leaves are visible + kVisOneLevel: JSROOT.BIT(4), // first level daughters are visible + kVisStreamed: JSROOT.BIT(5), // true if attributes have been streamed + kVisTouched: JSROOT.BIT(6), // true if attributes are changed after closing geom + kVisOnScreen: JSROOT.BIT(7), // true if volume is visible on screen + kVisContainers: JSROOT.BIT(12), // all containers visible + kVisOnly: JSROOT.BIT(13), // just this visible + kVisBranch: JSROOT.BIT(14), // only a given branch visible + kVisRaytrace: JSROOT.BIT(15) // raytracing flag +}; - "use strict"; +/** @memberOf GEO */ +function TestBit(volume, f) { + var att = volume.fGeoAtt; + return att === undefined ? false : ((att & f) !== 0); +} - /** @namespace JSROOT.GEO */ - /// Holder of all TGeo-related functions and classes - JSROOT.GEO = { - GradPerSegm: 6, // grad per segment in cylinder/spherical symmetry shapes - CompressComp: true, // use faces compression in composite shapes - CompLimit: 20 // maximal number of components in composite shape - }; - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.BITS = { - kVisOverride: JSROOT.BIT(0), // volume's vis. attributes are overwritten - kVisNone: JSROOT.BIT(1), // the volume/node is invisible, as well as daughters - kVisThis: JSROOT.BIT(2), // this volume/node is visible - kVisDaughters: JSROOT.BIT(3), // all leaves are visible - kVisOneLevel: JSROOT.BIT(4), // first level daughters are visible - kVisStreamed: JSROOT.BIT(5), // true if attributes have been streamed - kVisTouched: JSROOT.BIT(6), // true if attributes are changed after closing geom - kVisOnScreen: JSROOT.BIT(7), // true if volume is visible on screen - kVisContainers: JSROOT.BIT(12), // all containers visible - kVisOnly: JSROOT.BIT(13), // just this visible - kVisBranch: JSROOT.BIT(14), // only a given branch visible - kVisRaytrace: JSROOT.BIT(15) // raytracing flag - }; +/** @memberOf GEO */ +function SetBit(volume, f, value) { + if (volume.fGeoAtt === undefined) return; + volume.fGeoAtt = value ? (volume.fGeoAtt | f) : (volume.fGeoAtt & ~f); +} - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.TestBit = function (volume, f) { - var att = volume.fGeoAtt; - return att === undefined ? false : ((att & f) !== 0); - }; +/** @memberOf GEO */ +function ToggleBit(volume, f) { + if (volume.fGeoAtt !== undefined) + volume.fGeoAtt = volume.fGeoAtt ^ (f & 0xffffff); +} - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.SetBit = function (volume, f, value) { - if (volume.fGeoAtt === undefined) return; - volume.fGeoAtt = value ? (volume.fGeoAtt | f) : (volume.fGeoAtt & ~f); - }; +/** @memberOf GEO + * implementation of TGeoVolume::InvisibleAll */ +function InvisibleAll(flag) { + if (flag === undefined) flag = true; - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.ToggleBit = function (volume, f) { - if (volume.fGeoAtt !== undefined) - volume.fGeoAtt = volume.fGeoAtt ^ (f & 0xffffff); - }; + SetBit(this, BITS.kVisThis, !flag); + SetBit(this, BITS.kVisDaughters, !flag); + SetBit(this, BITS.kVisOneLevel, false); - /** @memberOf JSROOT.GEO - * implementation of TGeoVolume::InvisibleAll */ - JSROOT.GEO.InvisibleAll = function (flag) { - if (flag === undefined) flag = true; + if (this.fNodes) + for (var n = 0; n < this.fNodes.arr.length; ++n) { + var sub = this.fNodes.arr[n].fVolume; + SetBit(sub, BITS.kVisThis, !flag); + // SetBit(sub, BITS.kVisDaughters, !flag); + //SetBit(sub, BITS.kVisOneLevel, false); + } +} - JSROOT.GEO.SetBit(this, JSROOT.GEO.BITS.kVisThis, !flag); - JSROOT.GEO.SetBit(this, JSROOT.GEO.BITS.kVisDaughters, !flag); - JSROOT.GEO.SetBit(this, JSROOT.GEO.BITS.kVisOneLevel, false); +/** method used to avoid duplication of warnings + * @memberOf GEO */ +function warn(msg) { + if (_warn_msgs === undefined) _warn_msgs = {}; + if (_warn_msgs[msg] !== undefined) return; + _warn_msgs[msg] = true; + console.warn(msg); +} - if (this.fNodes) - for (var n = 0; n < this.fNodes.arr.length; ++n) { - var sub = this.fNodes.arr[n].fVolume; - JSROOT.GEO.SetBit(sub, JSROOT.GEO.BITS.kVisThis, !flag); - // JSROOT.GEO.SetBit(sub, JSROOT.GEO.BITS.kVisDaughters, !flag); - //JSROOT.GEO.SetBit(sub, JSROOT.GEO.BITS.kVisOneLevel, false); - } - }; +/** @memberOf GEO */ +function NodeKind(obj) { + // return kind of the geo nodes + // 0 - TGeoNode + // 1 - TEveGeoNode + // -1 - unsupported type - /** method used to avoid duplication of warnings - * @memberOf JSROOT.GEO */ - JSROOT.GEO.warn = function (msg) { - if (JSROOT.GEO._warn_msgs === undefined) JSROOT.GEO._warn_msgs = {}; - if (JSROOT.GEO._warn_msgs[msg] !== undefined) return; - JSROOT.GEO._warn_msgs[msg] = true; - console.warn(msg); - }; + if ((obj === undefined) || (obj === null) || (typeof obj !== 'object')) return -1; - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.NodeKind = function (obj) { - // return kind of the geo nodes - // 0 - TGeoNode - // 1 - TEveGeoNode - // -1 - unsupported type + return ('fShape' in obj) && ('fTrans' in obj) ? 1 : 0; +} - if ((obj === undefined) || (obj === null) || (typeof obj !== 'object')) return -1; +function CountNumShapes(shape) { + if (!shape) return 0; + if (shape._typename !== 'TGeoCompositeShape') return 1; + return CountNumShapes(shape.fNode.fLeft) + CountNumShapes(shape.fNode.fRight); +} - return ('fShape' in obj) && ('fTrans' in obj) ? 1 : 0; - }; +// ========================================================================== - JSROOT.GEO.CountNumShapes = function (shape) { - if (!shape) return 0; - if (shape._typename !== 'TGeoCompositeShape') return 1; - return JSROOT.GEO.CountNumShapes(shape.fNode.fLeft) + JSROOT.GEO.CountNumShapes(shape.fNode.fRight); - }; - - // ========================================================================== - - JSROOT.GEO.GeometryCreator = function (numfaces) { +class GeometryCreator { + constructor(numfaces) { this.nfaces = numfaces; this.indx = 0; this.pos = new Float32Array(numfaces * 9); this.norm = new Float32Array(numfaces * 9); + } +} - return this; - }; +GeometryCreator.prototype.AddFace3 = function (x1, y1, z1, + x2, y2, z2, + x3, y3, z3) { + var indx = this.indx, pos = this.pos; + pos[indx] = x1; + pos[indx + 1] = y1; + pos[indx + 2] = z1; + pos[indx + 3] = x2; + pos[indx + 4] = y2; + pos[indx + 5] = z2; + pos[indx + 6] = x3; + pos[indx + 7] = y3; + pos[indx + 8] = z3; + this.last4 = false; + this.indx = indx + 9; +}; - JSROOT.GEO.GeometryCreator.prototype.AddFace3 = function (x1, y1, z1, - x2, y2, z2, - x3, y3, z3) { - var indx = this.indx, pos = this.pos; +GeometryCreator.prototype.StartPolygon = function () { +}; +GeometryCreator.prototype.StopPolygon = function () { +}; + +GeometryCreator.prototype.AddFace4 = function (x1, y1, z1, + x2, y2, z2, + x3, y3, z3, + x4, y4, z4, + reduce) { + // from four vertices one normally creates two faces (1,2,3) and (1,3,4) + // if (reduce==1), first face is reduced + // if (reduce==2), second face is reduced + + var indx = this.indx, pos = this.pos; + + if (reduce !== 1) { pos[indx] = x1; pos[indx + 1] = y1; pos[indx + 2] = z1; @@ -134,2013 +145,1989 @@ pos[indx + 6] = x3; pos[indx + 7] = y3; pos[indx + 8] = z3; - this.last4 = false; - this.indx = indx + 9; - }; + indx += 9; + } - JSROOT.GEO.GeometryCreator.prototype.StartPolygon = function () { - }; - JSROOT.GEO.GeometryCreator.prototype.StopPolygon = function () { - }; + if (reduce !== 2) { + pos[indx] = x1; + pos[indx + 1] = y1; + pos[indx + 2] = z1; + pos[indx + 3] = x3; + pos[indx + 4] = y3; + pos[indx + 5] = z3; + pos[indx + 6] = x4; + pos[indx + 7] = y4; + pos[indx + 8] = z4; + indx += 9; + } - JSROOT.GEO.GeometryCreator.prototype.AddFace4 = function (x1, y1, z1, - x2, y2, z2, - x3, y3, z3, - x4, y4, z4, - reduce) { - // from four vertices one normally creates two faces (1,2,3) and (1,3,4) - // if (reduce==1), first face is reduced - // if (reduce==2), second face is reduced + this.last4 = (indx !== this.indx + 9); + this.indx = indx; +}; - var indx = this.indx, pos = this.pos; +GeometryCreator.prototype.SetNormal4 = function (nx1, ny1, nz1, + nx2, ny2, nz2, + nx3, ny3, nz3, + nx4, ny4, nz4, + reduce) { + // same as AddFace4, assign normals for each individual vertex + // reduce has same meaning and should be the same - if (reduce !== 1) { - pos[indx] = x1; - pos[indx + 1] = y1; - pos[indx + 2] = z1; - pos[indx + 3] = x2; - pos[indx + 4] = y2; - pos[indx + 5] = z2; - pos[indx + 6] = x3; - pos[indx + 7] = y3; - pos[indx + 8] = z3; - indx += 9; - } + if (this.last4 && reduce) + return console.error('missmatch between AddFace4 and SetNormal4 calls'); - if (reduce !== 2) { - pos[indx] = x1; - pos[indx + 1] = y1; - pos[indx + 2] = z1; - pos[indx + 3] = x3; - pos[indx + 4] = y3; - pos[indx + 5] = z3; - pos[indx + 6] = x4; - pos[indx + 7] = y4; - pos[indx + 8] = z4; - indx += 9; - } + var indx = this.indx - (this.last4 ? 18 : 9), norm = this.norm; - this.last4 = (indx !== this.indx + 9); - this.indx = indx; - }; + if (reduce !== 1) { + norm[indx] = nx1; + norm[indx + 1] = ny1; + norm[indx + 2] = nz1; + norm[indx + 3] = nx2; + norm[indx + 4] = ny2; + norm[indx + 5] = nz2; + norm[indx + 6] = nx3; + norm[indx + 7] = ny3; + norm[indx + 8] = nz3; + indx += 9; + } - JSROOT.GEO.GeometryCreator.prototype.SetNormal4 = function (nx1, ny1, nz1, - nx2, ny2, nz2, - nx3, ny3, nz3, - nx4, ny4, nz4, - reduce) { - // same as AddFace4, assign normals for each individual vertex - // reduce has same meaning and should be the same + if (reduce !== 2) { + norm[indx] = nx1; + norm[indx + 1] = ny1; + norm[indx + 2] = nz1; + norm[indx + 3] = nx3; + norm[indx + 4] = ny3; + norm[indx + 5] = nz3; + norm[indx + 6] = nx4; + norm[indx + 7] = ny4; + norm[indx + 8] = nz4; + } +}; - if (this.last4 && reduce) - return console.error('missmatch between AddFace4 and SetNormal4 calls'); +GeometryCreator.prototype.RecalcZ = function (func) { + var pos = this.pos, + last = this.indx, + indx = last - (this.last4 ? 18 : 9); - var indx = this.indx - (this.last4 ? 18 : 9), norm = this.norm; + while (indx < last) { + pos[indx + 2] = func(pos[indx], pos[indx + 1], pos[indx + 2]); + indx += 3; + } +}; - if (reduce !== 1) { - norm[indx] = nx1; - norm[indx + 1] = ny1; - norm[indx + 2] = nz1; - norm[indx + 3] = nx2; - norm[indx + 4] = ny2; - norm[indx + 5] = nz2; - norm[indx + 6] = nx3; - norm[indx + 7] = ny3; - norm[indx + 8] = nz3; - indx += 9; - } +function GetNormal(x1, y1, z1, x2, y2, z2, x3, y3, z3) { - if (reduce !== 2) { - norm[indx] = nx1; - norm[indx + 1] = ny1; - norm[indx + 2] = nz1; - norm[indx + 3] = nx3; - norm[indx + 4] = ny3; - norm[indx + 5] = nz3; - norm[indx + 6] = nx4; - norm[indx + 7] = ny4; - norm[indx + 8] = nz4; - } - }; + var pA = new THREE.Vector3(x1, y1, z1), + pB = new THREE.Vector3(x2, y2, z2), + pC = new THREE.Vector3(x3, y3, z3), + cb = new THREE.Vector3(), + ab = new THREE.Vector3(); - JSROOT.GEO.GeometryCreator.prototype.RecalcZ = function (func) { - var pos = this.pos, - last = this.indx, - indx = last - (this.last4 ? 18 : 9); + cb.subVectors(pC, pB); + ab.subVectors(pA, pB); + cb.cross(ab); - while (indx < last) { - pos[indx + 2] = func(pos[indx], pos[indx + 1], pos[indx + 2]); - indx += 3; - } - }; + return cb; +} - JSROOT.GEO.GetNormal = function (x1, y1, z1, x2, y2, z2, x3, y3, z3) { +GeometryCreator.prototype.CalcNormal = function () { + var indx = this.indx, norm = this.norm; - var pA = new THREE.Vector3(x1, y1, z1), - pB = new THREE.Vector3(x2, y2, z2), - pC = new THREE.Vector3(x3, y3, z3), - cb = new THREE.Vector3(), - ab = new THREE.Vector3(); + if (!this.cb) { + this.pA = new THREE.Vector3(); + this.pB = new THREE.Vector3(); + this.pC = new THREE.Vector3(); + this.cb = new THREE.Vector3(); + this.ab = new THREE.Vector3(); + } - cb.subVectors(pC, pB); - ab.subVectors(pA, pB); - cb.cross(ab); + this.pA.fromArray(this.pos, this.indx - 9); + this.pB.fromArray(this.pos, this.indx - 6); + this.pC.fromArray(this.pos, this.indx - 3); - return cb; - }; + this.cb.subVectors(this.pC, this.pB); + this.ab.subVectors(this.pA, this.pB); + this.cb.cross(this.ab); - JSROOT.GEO.GeometryCreator.prototype.CalcNormal = function () { - var indx = this.indx, norm = this.norm; + this.SetNormal(this.cb.x, this.cb.y, this.cb.z); +}; - if (!this.cb) { - this.pA = new THREE.Vector3(); - this.pB = new THREE.Vector3(); - this.pC = new THREE.Vector3(); - this.cb = new THREE.Vector3(); - this.ab = new THREE.Vector3(); - } +GeometryCreator.prototype.SetNormal = function (nx, ny, nz) { + var indx = this.indx - 9, norm = this.norm; - this.pA.fromArray(this.pos, this.indx - 9); - this.pB.fromArray(this.pos, this.indx - 6); - this.pC.fromArray(this.pos, this.indx - 3); - - this.cb.subVectors(this.pC, this.pB); - this.ab.subVectors(this.pA, this.pB); - this.cb.cross(this.ab); - - this.SetNormal(this.cb.x, this.cb.y, this.cb.z); - }; - - JSROOT.GEO.GeometryCreator.prototype.SetNormal = function (nx, ny, nz) { - var indx = this.indx - 9, norm = this.norm; + norm[indx] = norm[indx + 3] = norm[indx + 6] = nx; + norm[indx + 1] = norm[indx + 4] = norm[indx + 7] = ny; + norm[indx + 2] = norm[indx + 5] = norm[indx + 8] = nz; + if (this.last4) { + indx -= 9; norm[indx] = norm[indx + 3] = norm[indx + 6] = nx; norm[indx + 1] = norm[indx + 4] = norm[indx + 7] = ny; norm[indx + 2] = norm[indx + 5] = norm[indx + 8] = nz; + } +}; - if (this.last4) { - indx -= 9; - norm[indx] = norm[indx + 3] = norm[indx + 6] = nx; - norm[indx + 1] = norm[indx + 4] = norm[indx + 7] = ny; - norm[indx + 2] = norm[indx + 5] = norm[indx + 8] = nz; - } - }; +GeometryCreator.prototype.SetNormal_12_34 = function (nx12, ny12, nz12, nx34, ny34, nz34, reduce) { + // special shortcut, when same normals can be applied for 1-2 point and 3-4 point + if (reduce === undefined) reduce = 0; - JSROOT.GEO.GeometryCreator.prototype.SetNormal_12_34 = function (nx12, ny12, nz12, nx34, ny34, nz34, reduce) { - // special shortcut, when same normals can be applied for 1-2 point and 3-4 point - if (reduce === undefined) reduce = 0; + var indx = this.indx - ((reduce > 0) ? 9 : 18), norm = this.norm; - var indx = this.indx - ((reduce > 0) ? 9 : 18), norm = this.norm; + if (reduce !== 1) { + norm[indx] = nx12; + norm[indx + 1] = ny12; + norm[indx + 2] = nz12; + norm[indx + 3] = nx12; + norm[indx + 4] = ny12; + norm[indx + 5] = nz12; + norm[indx + 6] = nx34; + norm[indx + 7] = ny34; + norm[indx + 8] = nz34; + indx += 9; + } - if (reduce !== 1) { - norm[indx] = nx12; - norm[indx + 1] = ny12; - norm[indx + 2] = nz12; - norm[indx + 3] = nx12; - norm[indx + 4] = ny12; - norm[indx + 5] = nz12; - norm[indx + 6] = nx34; - norm[indx + 7] = ny34; - norm[indx + 8] = nz34; - indx += 9; - } + if (reduce !== 2) { + norm[indx] = nx12; + norm[indx + 1] = ny12; + norm[indx + 2] = nz12; + norm[indx + 3] = nx34; + norm[indx + 4] = ny34; + norm[indx + 5] = nz34; + norm[indx + 6] = nx34; + norm[indx + 7] = ny34; + norm[indx + 8] = nz34; + indx += 9; + } +}; - if (reduce !== 2) { - norm[indx] = nx12; - norm[indx + 1] = ny12; - norm[indx + 2] = nz12; - norm[indx + 3] = nx34; - norm[indx + 4] = ny34; - norm[indx + 5] = nz34; - norm[indx + 6] = nx34; - norm[indx + 7] = ny34; - norm[indx + 8] = nz34; - indx += 9; - } - }; +GeometryCreator.prototype.Create = function () { + if (this.nfaces !== this.indx / 9) + console.error('Mismatch with created ' + this.nfaces + ' and filled ' + this.indx / 9 + ' number of faces'); - JSROOT.GEO.GeometryCreator.prototype.Create = function () { - if (this.nfaces !== this.indx / 9) - console.error('Mismatch with created ' + this.nfaces + ' and filled ' + this.indx / 9 + ' number of faces'); + var geometry = new THREE.BufferGeometry(); + geometry.addAttribute('position', new THREE.BufferAttribute(this.pos, 3)); + geometry.addAttribute('normal', new THREE.BufferAttribute(this.norm, 3)); + return geometry; +}; - var geometry = new THREE.BufferGeometry(); - geometry.addAttribute('position', new THREE.BufferAttribute(this.pos, 3)); - geometry.addAttribute('normal', new THREE.BufferAttribute(this.norm, 3)); - return geometry; - }; +// ================================================================================ - // ================================================================================ +// same methods as GeometryCreator, but with different implementation - // same methods as GeometryCreator, but with different implementation - - JSROOT.GEO.PolygonsCreator = function () { +class PolygonsCreator { + constructor() { this.polygons = []; - }; + } +} - JSROOT.GEO.PolygonsCreator.prototype.StartPolygon = function (normal) { - this.multi = 1; - this.mnormal = normal; - }; +PolygonsCreator.prototype.StartPolygon = function (normal) { + this.multi = 1; + this.mnormal = normal; +}; - JSROOT.GEO.PolygonsCreator.prototype.StopPolygon = function () { - if (!this.multi) return; - this.multi = 0; - console.error('Polygon should be already closed at this moment'); - }; +PolygonsCreator.prototype.StopPolygon = function () { + if (!this.multi) return; + this.multi = 0; + console.error('Polygon should be already closed at this moment'); +}; - JSROOT.GEO.PolygonsCreator.prototype.AddFace3 = function (x1, y1, z1, - x2, y2, z2, - x3, y3, z3) { - this.AddFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x3, y3, z3, 2); - }; +PolygonsCreator.prototype.AddFace3 = function (x1, y1, z1, + x2, y2, z2, + x3, y3, z3) { + this.AddFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x3, y3, z3, 2); +}; - JSROOT.GEO.PolygonsCreator.prototype.AddFace4 = function (x1, y1, z1, - x2, y2, z2, - x3, y3, z3, - x4, y4, z4, - reduce) { - // from four vertices one normally creates two faces (1,2,3) and (1,3,4) - // if (reduce==1), first face is reduced - // if (reduce==2), second face is reduced +PolygonsCreator.prototype.AddFace4 = function (x1, y1, z1, + x2, y2, z2, + x3, y3, z3, + x4, y4, z4, + reduce) { + // from four vertices one normally creates two faces (1,2,3) and (1,3,4) + // if (reduce==1), first face is reduced + // if (reduce==2), second face is reduced - if (reduce === undefined) reduce = 0; + if (reduce === undefined) reduce = 0; - this.v1 = new ThreeBSP.Vertex(x1, y1, z1, 0, 0, 0); - this.v2 = (reduce === 1) ? null : new ThreeBSP.Vertex(x2, y2, z2, 0, 0, 0); - this.v3 = new ThreeBSP.Vertex(x3, y3, z3, 0, 0, 0); - this.v4 = (reduce === 2) ? null : new ThreeBSP.Vertex(x4, y4, z4, 0, 0, 0); + this.v1 = new ThreeBSP.Vertex(x1, y1, z1, 0, 0, 0); + this.v2 = (reduce === 1) ? null : new ThreeBSP.Vertex(x2, y2, z2, 0, 0, 0); + this.v3 = new ThreeBSP.Vertex(x3, y3, z3, 0, 0, 0); + this.v4 = (reduce === 2) ? null : new ThreeBSP.Vertex(x4, y4, z4, 0, 0, 0); - this.reduce = reduce; + this.reduce = reduce; - if (this.multi) { + if (this.multi) { - if (reduce !== 2) console.error('polygon not supported for not-reduced faces'); + if (reduce !== 2) console.error('polygon not supported for not-reduced faces'); - var polygon; + var polygon; - if (this.multi++ === 1) { - polygon = new ThreeBSP.Polygon; + if (this.multi++ === 1) { + polygon = new ThreeBSP.Polygon; - polygon.vertices.push(this.mnormal ? this.v2 : this.v3); - this.polygons.push(polygon); - } else { - polygon = this.polygons[this.polygons.length - 1]; - // check that last vertice equals to v2 - var last = this.mnormal ? polygon.vertices[polygon.vertices.length - 1] : polygon.vertices[0], - comp = this.mnormal ? this.v2 : this.v3; - - if (comp.diff(last) > 1e-12) - console.error('vertex missmatch when building polygon'); - } - - var first = this.mnormal ? polygon.vertices[0] : polygon.vertices[polygon.vertices.length - 1], - next = this.mnormal ? this.v3 : this.v2; - - if (next.diff(first) < 1e-12) { - //console.log('polygon closed!!!', polygon.vertices.length); - this.multi = 0; - } else if (this.mnormal) { - polygon.vertices.push(this.v3); - } else { - polygon.vertices.unshift(this.v2); - } - - return; - - } - - var polygon = new ThreeBSP.Polygon; - - switch (reduce) { - case 0: - polygon.vertices.push(this.v1, this.v2, this.v3, this.v4); - break; - case 1: - polygon.vertices.push(this.v1, this.v3, this.v4); - break; - case 2: - polygon.vertices.push(this.v1, this.v2, this.v3); - break; - } - - this.polygons.push(polygon); - }; - - JSROOT.GEO.PolygonsCreator.prototype.SetNormal4 = function (nx1, ny1, nz1, - nx2, ny2, nz2, - nx3, ny3, nz3, - nx4, ny4, nz4, - reduce) { - this.v1.setnormal(nx1, ny1, nz1); - if (this.v2) this.v2.setnormal(nx2, ny2, nz2); - this.v3.setnormal(nx3, ny3, nz3); - if (this.v4) this.v4.setnormal(nx4, ny4, nz4); - }; - - JSROOT.GEO.PolygonsCreator.prototype.SetNormal_12_34 = function (nx12, ny12, nz12, nx34, ny34, nz34, reduce) { - // special shortcut, when same normals can be applied for 1-2 point and 3-4 point - this.v1.setnormal(nx12, ny12, nz12); - if (this.v2) this.v2.setnormal(nx12, ny12, nz12); - this.v3.setnormal(nx34, ny34, nz34); - if (this.v4) this.v4.setnormal(nx34, ny34, nz34); - }; - - JSROOT.GEO.PolygonsCreator.prototype.CalcNormal = function () { - - if (!this.cb) { - this.pA = new THREE.Vector3(); - this.pB = new THREE.Vector3(); - this.pC = new THREE.Vector3(); - this.cb = new THREE.Vector3(); - this.ab = new THREE.Vector3(); - } - - this.pA.set(this.v1.x, this.v1.y, this.v1.z); - - if (this.reduce !== 1) { - this.pB.set(this.v2.x, this.v2.y, this.v2.z); - this.pC.set(this.v3.x, this.v3.y, this.v3.z); + polygon.vertices.push(this.mnormal ? this.v2 : this.v3); + this.polygons.push(polygon); } else { - this.pB.set(this.v3.x, this.v3.y, this.v3.z); - this.pC.set(this.v4.x, this.v4.y, this.v4.z); + polygon = this.polygons[this.polygons.length - 1]; + // check that last vertice equals to v2 + var last = this.mnormal ? polygon.vertices[polygon.vertices.length - 1] : polygon.vertices[0], + comp = this.mnormal ? this.v2 : this.v3; + + if (comp.diff(last) > 1e-12) + console.error('vertex missmatch when building polygon'); } - this.cb.subVectors(this.pC, this.pB); - this.ab.subVectors(this.pA, this.pB); - this.cb.cross(this.ab); + var first = this.mnormal ? polygon.vertices[0] : polygon.vertices[polygon.vertices.length - 1], + next = this.mnormal ? this.v3 : this.v2; - this.SetNormal(this.cb.x, this.cb.y, this.cb.z); - }; - - - JSROOT.GEO.PolygonsCreator.prototype.SetNormal = function (nx, ny, nz) { - this.v1.setnormal(nx, ny, nz); - if (this.v2) this.v2.setnormal(nx, ny, nz); - this.v3.setnormal(nx, ny, nz); - if (this.v4) this.v4.setnormal(nx, ny, nz); - }; - - JSROOT.GEO.PolygonsCreator.prototype.RecalcZ = function (func) { - this.v1.z = func(this.v1.x, this.v1.y, this.v1.z); - if (this.v2) this.v2.z = func(this.v2.x, this.v2.y, this.v2.z); - this.v3.z = func(this.v3.x, this.v3.y, this.v3.z); - if (this.v4) this.v4.z = func(this.v4.x, this.v4.y, this.v4.z); - }; - - JSROOT.GEO.PolygonsCreator.prototype.Create = function () { - return {polygons: this.polygons}; - }; - - // ================= all functions to create geometry =================================== - - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.createCubeBuffer = function (shape, faces_limit) { - - if (faces_limit < 0) return 12; - - var dx = shape.fDX, dy = shape.fDY, dz = shape.fDZ; - - var creator = faces_limit ? new JSROOT.GEO.PolygonsCreator : new JSROOT.GEO.GeometryCreator(12); - - creator.AddFace4(dx, dy, dz, dx, -dy, dz, dx, -dy, -dz, dx, dy, -dz); - creator.SetNormal(1, 0, 0); - - creator.AddFace4(-dx, dy, -dz, -dx, -dy, -dz, -dx, -dy, dz, -dx, dy, dz); - creator.SetNormal(-1, 0, 0); - - creator.AddFace4(-dx, dy, -dz, -dx, dy, dz, dx, dy, dz, dx, dy, -dz); - creator.SetNormal(0, 1, 0); - - creator.AddFace4(-dx, -dy, dz, -dx, -dy, -dz, dx, -dy, -dz, dx, -dy, dz); - creator.SetNormal(0, -1, 0); - - creator.AddFace4(-dx, dy, dz, -dx, -dy, dz, dx, -dy, dz, dx, dy, dz); - creator.SetNormal(0, 0, 1); - - creator.AddFace4(dx, dy, -dz, dx, -dy, -dz, -dx, -dy, -dz, -dx, dy, -dz); - creator.SetNormal(0, 0, -1); - - return creator.Create(); - }; - - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.create8edgesBuffer = function (v, faces_limit) { - - var indicies = [4, 7, 6, 5, 0, 3, 7, 4, 4, 5, 1, 0, 6, 2, 1, 5, 7, 3, 2, 6, 1, 2, 3, 0]; - - var creator = (faces_limit > 0) ? new JSROOT.GEO.PolygonsCreator : new JSROOT.GEO.GeometryCreator(12); - - for (var n = 0; n < indicies.length; n += 4) { - var i1 = indicies[n] * 3, - i2 = indicies[n + 1] * 3, - i3 = indicies[n + 2] * 3, - i4 = indicies[n + 3] * 3; - creator.AddFace4(v[i1], v[i1 + 1], v[i1 + 2], v[i2], v[i2 + 1], v[i2 + 2], - v[i3], v[i3 + 1], v[i3 + 2], v[i4], v[i4 + 1], v[i4 + 2]); - if (n === 0) creator.SetNormal(0, 0, 1); else if (n === 20) creator.SetNormal(0, 0, -1); else creator.CalcNormal(); - } - - return creator.Create(); - }; - - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.createParaBuffer = function (shape, faces_limit) { - - if (faces_limit < 0) return 12; - - var txy = shape.fTxy, txz = shape.fTxz, tyz = shape.fTyz; - - var v = [ - -shape.fZ * txz - txy * shape.fY - shape.fX, -shape.fY - shape.fZ * tyz, -shape.fZ, - -shape.fZ * txz + txy * shape.fY - shape.fX, shape.fY - shape.fZ * tyz, -shape.fZ, - -shape.fZ * txz + txy * shape.fY + shape.fX, shape.fY - shape.fZ * tyz, -shape.fZ, - -shape.fZ * txz - txy * shape.fY + shape.fX, -shape.fY - shape.fZ * tyz, -shape.fZ, - shape.fZ * txz - txy * shape.fY - shape.fX, -shape.fY + shape.fZ * tyz, shape.fZ, - shape.fZ * txz + txy * shape.fY - shape.fX, shape.fY + shape.fZ * tyz, shape.fZ, - shape.fZ * txz + txy * shape.fY + shape.fX, shape.fY + shape.fZ * tyz, shape.fZ, - shape.fZ * txz - txy * shape.fY + shape.fX, -shape.fY + shape.fZ * tyz, shape.fZ]; - - return JSROOT.GEO.create8edgesBuffer(v, faces_limit); - }; - - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.createTrapezoidBuffer = function (shape, faces_limit) { - - if (faces_limit < 0) return 12; - - var y1, y2; - if (shape._typename == "TGeoTrd1") { - y1 = y2 = shape.fDY; + if (next.diff(first) < 1e-12) { + //console.log('polygon closed!!!', polygon.vertices.length); + this.multi = 0; + } else if (this.mnormal) { + polygon.vertices.push(this.v3); } else { - y1 = shape.fDy1; - y2 = shape.fDy2; + polygon.vertices.unshift(this.v2); } - var v = [ - -shape.fDx1, y1, -shape.fDZ, - shape.fDx1, y1, -shape.fDZ, - shape.fDx1, -y1, -shape.fDZ, - -shape.fDx1, -y1, -shape.fDZ, - -shape.fDx2, y2, shape.fDZ, - shape.fDx2, y2, shape.fDZ, - shape.fDx2, -y2, shape.fDZ, - -shape.fDx2, -y2, shape.fDZ - ]; + return; - return JSROOT.GEO.create8edgesBuffer(v, faces_limit); - }; + } + + var polygon = new ThreeBSP.Polygon; + + switch (reduce) { + case 0: + polygon.vertices.push(this.v1, this.v2, this.v3, this.v4); + break; + case 1: + polygon.vertices.push(this.v1, this.v3, this.v4); + break; + case 2: + polygon.vertices.push(this.v1, this.v2, this.v3); + break; + } + + this.polygons.push(polygon); +}; + +PolygonsCreator.prototype.SetNormal4 = function (nx1, ny1, nz1, + nx2, ny2, nz2, + nx3, ny3, nz3, + nx4, ny4, nz4, + reduce) { + this.v1.setnormal(nx1, ny1, nz1); + if (this.v2) this.v2.setnormal(nx2, ny2, nz2); + this.v3.setnormal(nx3, ny3, nz3); + if (this.v4) this.v4.setnormal(nx4, ny4, nz4); +}; + +PolygonsCreator.prototype.SetNormal_12_34 = function (nx12, ny12, nz12, nx34, ny34, nz34, reduce) { + // special shortcut, when same normals can be applied for 1-2 point and 3-4 point + this.v1.setnormal(nx12, ny12, nz12); + if (this.v2) this.v2.setnormal(nx12, ny12, nz12); + this.v3.setnormal(nx34, ny34, nz34); + if (this.v4) this.v4.setnormal(nx34, ny34, nz34); +}; + +PolygonsCreator.prototype.CalcNormal = function () { + + if (!this.cb) { + this.pA = new THREE.Vector3(); + this.pB = new THREE.Vector3(); + this.pC = new THREE.Vector3(); + this.cb = new THREE.Vector3(); + this.ab = new THREE.Vector3(); + } + + this.pA.set(this.v1.x, this.v1.y, this.v1.z); + + if (this.reduce !== 1) { + this.pB.set(this.v2.x, this.v2.y, this.v2.z); + this.pC.set(this.v3.x, this.v3.y, this.v3.z); + } else { + this.pB.set(this.v3.x, this.v3.y, this.v3.z); + this.pC.set(this.v4.x, this.v4.y, this.v4.z); + } + + this.cb.subVectors(this.pC, this.pB); + this.ab.subVectors(this.pA, this.pB); + this.cb.cross(this.ab); + + this.SetNormal(this.cb.x, this.cb.y, this.cb.z); +}; - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.createArb8Buffer = function (shape, faces_limit) { +PolygonsCreator.prototype.SetNormal = function (nx, ny, nz) { + this.v1.setnormal(nx, ny, nz); + if (this.v2) this.v2.setnormal(nx, ny, nz); + this.v3.setnormal(nx, ny, nz); + if (this.v4) this.v4.setnormal(nx, ny, nz); +}; - if (faces_limit < 0) return 12; +PolygonsCreator.prototype.RecalcZ = function (func) { + this.v1.z = func(this.v1.x, this.v1.y, this.v1.z); + if (this.v2) this.v2.z = func(this.v2.x, this.v2.y, this.v2.z); + this.v3.z = func(this.v3.x, this.v3.y, this.v3.z); + if (this.v4) this.v4.z = func(this.v4.x, this.v4.y, this.v4.z); +}; - var vertices = [ - shape.fXY[0][0], shape.fXY[0][1], -shape.fDZ, - shape.fXY[1][0], shape.fXY[1][1], -shape.fDZ, - shape.fXY[2][0], shape.fXY[2][1], -shape.fDZ, - shape.fXY[3][0], shape.fXY[3][1], -shape.fDZ, - shape.fXY[4][0], shape.fXY[4][1], shape.fDZ, - shape.fXY[5][0], shape.fXY[5][1], shape.fDZ, - shape.fXY[6][0], shape.fXY[6][1], shape.fDZ, - shape.fXY[7][0], shape.fXY[7][1], shape.fDZ - ], - indicies = [ - 4, 7, 6, 6, 5, 4, 0, 3, 7, 7, 4, 0, - 4, 5, 1, 1, 0, 4, 6, 2, 1, 1, 5, 6, - 7, 3, 2, 2, 6, 7, 1, 2, 3, 3, 0, 1]; +PolygonsCreator.prototype.Create = function () { + return {polygons: this.polygons}; +}; - // detect same vertices on both Z-layers - for (var side = 0; side < vertices.length; side += vertices.length / 2) - for (var n1 = side; n1 < side + vertices.length / 2 - 3; n1 += 3) - for (var n2 = n1 + 3; n2 < side + vertices.length / 2; n2 += 3) - if ((vertices[n1] === vertices[n2]) && - (vertices[n1 + 1] === vertices[n2 + 1]) && - (vertices[n1 + 2] === vertices[n2 + 2])) { - for (var k = 0; k < indicies.length; ++k) - if (indicies[k] === n2 / 3) indicies[k] = n1 / 3; - } +// ================= all functions to create geometry =================================== + +/** @memberOf GEO */ +export function createCubeBuffer(shape, faces_limit) { + + if (faces_limit < 0) return 12; + + var dx = shape.fDX, dy = shape.fDY, dz = shape.fDZ; + + var creator = faces_limit ? new PolygonsCreator : new GeometryCreator(12); + + creator.AddFace4(dx, dy, dz, dx, -dy, dz, dx, -dy, -dz, dx, dy, -dz); + creator.SetNormal(1, 0, 0); + + creator.AddFace4(-dx, dy, -dz, -dx, -dy, -dz, -dx, -dy, dz, -dx, dy, dz); + creator.SetNormal(-1, 0, 0); + + creator.AddFace4(-dx, dy, -dz, -dx, dy, dz, dx, dy, dz, dx, dy, -dz); + creator.SetNormal(0, 1, 0); + + creator.AddFace4(-dx, -dy, dz, -dx, -dy, -dz, dx, -dy, -dz, dx, -dy, dz); + creator.SetNormal(0, -1, 0); + + creator.AddFace4(-dx, dy, dz, -dx, -dy, dz, dx, -dy, dz, dx, dy, dz); + creator.SetNormal(0, 0, 1); + + creator.AddFace4(dx, dy, -dz, dx, -dy, -dz, -dx, -dy, -dz, -dx, dy, -dz); + creator.SetNormal(0, 0, -1); + + return creator.Create(); +} + +/** @memberOf GEO */ +export function create8edgesBuffer(v, faces_limit) { + + var indicies = [4, 7, 6, 5, 0, 3, 7, 4, 4, 5, 1, 0, 6, 2, 1, 5, 7, 3, 2, 6, 1, 2, 3, 0]; + + var creator = (faces_limit > 0) ? new PolygonsCreator : new GeometryCreator(12); + + for (var n = 0; n < indicies.length; n += 4) { + var i1 = indicies[n] * 3, + i2 = indicies[n + 1] * 3, + i3 = indicies[n + 2] * 3, + i4 = indicies[n + 3] * 3; + creator.AddFace4(v[i1], v[i1 + 1], v[i1 + 2], v[i2], v[i2 + 1], v[i2 + 2], + v[i3], v[i3 + 1], v[i3 + 2], v[i4], v[i4 + 1], v[i4 + 2]); + if (n === 0) creator.SetNormal(0, 0, 1); else if (n === 20) creator.SetNormal(0, 0, -1); else creator.CalcNormal(); + } + + return creator.Create(); +} + +/** @memberOf GEO */ +export function createParaBuffer(shape, faces_limit) { + + if (faces_limit < 0) return 12; + + var txy = shape.fTxy, txz = shape.fTxz, tyz = shape.fTyz; + + var v = [ + -shape.fZ * txz - txy * shape.fY - shape.fX, -shape.fY - shape.fZ * tyz, -shape.fZ, + -shape.fZ * txz + txy * shape.fY - shape.fX, shape.fY - shape.fZ * tyz, -shape.fZ, + -shape.fZ * txz + txy * shape.fY + shape.fX, shape.fY - shape.fZ * tyz, -shape.fZ, + -shape.fZ * txz - txy * shape.fY + shape.fX, -shape.fY - shape.fZ * tyz, -shape.fZ, + shape.fZ * txz - txy * shape.fY - shape.fX, -shape.fY + shape.fZ * tyz, shape.fZ, + shape.fZ * txz + txy * shape.fY - shape.fX, shape.fY + shape.fZ * tyz, shape.fZ, + shape.fZ * txz + txy * shape.fY + shape.fX, shape.fY + shape.fZ * tyz, shape.fZ, + shape.fZ * txz - txy * shape.fY + shape.fX, -shape.fY + shape.fZ * tyz, shape.fZ]; + + return create8edgesBuffer(v, faces_limit); +} + +/** @memberOf GEO */ +export function createTrapezoidBuffer(shape, faces_limit) { + + if (faces_limit < 0) return 12; + + var y1, y2; + if (shape._typename === "TGeoTrd1") { + y1 = y2 = shape.fDY; + } else { + y1 = shape.fDy1; + y2 = shape.fDy2; + } + + var v = [ + -shape.fDx1, y1, -shape.fDZ, + shape.fDx1, y1, -shape.fDZ, + shape.fDx1, -y1, -shape.fDZ, + -shape.fDx1, -y1, -shape.fDZ, + -shape.fDx2, y2, shape.fDZ, + shape.fDx2, y2, shape.fDZ, + shape.fDx2, -y2, shape.fDZ, + -shape.fDx2, -y2, shape.fDZ + ]; + + return create8edgesBuffer(v, faces_limit); +} - var map = [], // list of existing faces (with all rotations) - numfaces = 0; +/** @memberOf GEO */ +export function createArb8Buffer(shape, faces_limit) { - for (var k = 0; k < indicies.length; k += 3) { - var id1 = indicies[k] * 100 + indicies[k + 1] * 10 + indicies[k + 2], - id2 = indicies[k + 1] * 100 + indicies[k + 2] * 10 + indicies[k], - id3 = indicies[k + 2] * 100 + indicies[k] * 10 + indicies[k + 1]; + if (faces_limit < 0) return 12; - if ((indicies[k] == indicies[k + 1]) || (indicies[k] == indicies[k + 2]) || (indicies[k + 1] == indicies[k + 2]) || - (map.indexOf(id1) >= 0) || (map.indexOf(id2) >= 0) || (map.indexOf(id3) >= 0)) { - indicies[k] = indicies[k + 1] = indicies[k + 2] = -1; - } else { - map.push(id1, id2, id3); - numfaces++; - } - } + var vertices = [ + shape.fXY[0][0], shape.fXY[0][1], -shape.fDZ, + shape.fXY[1][0], shape.fXY[1][1], -shape.fDZ, + shape.fXY[2][0], shape.fXY[2][1], -shape.fDZ, + shape.fXY[3][0], shape.fXY[3][1], -shape.fDZ, + shape.fXY[4][0], shape.fXY[4][1], shape.fDZ, + shape.fXY[5][0], shape.fXY[5][1], shape.fDZ, + shape.fXY[6][0], shape.fXY[6][1], shape.fDZ, + shape.fXY[7][0], shape.fXY[7][1], shape.fDZ + ], + indicies = [ + 4, 7, 6, 6, 5, 4, 0, 3, 7, 7, 4, 0, + 4, 5, 1, 1, 0, 4, 6, 2, 1, 1, 5, 6, + 7, 3, 2, 2, 6, 7, 1, 2, 3, 3, 0, 1]; - var creator = faces_limit ? new JSROOT.GEO.PolygonsCreator : new JSROOT.GEO.GeometryCreator(numfaces); - - // var creator = new JSROOT.GEO.GeometryCreator(numfaces); - - for (var n = 0; n < indicies.length; n += 6) { - var i1 = indicies[n] * 3, - i2 = indicies[n + 1] * 3, - i3 = indicies[n + 2] * 3, - i4 = indicies[n + 3] * 3, - i5 = indicies[n + 4] * 3, - i6 = indicies[n + 5] * 3, - norm = null; - - if ((i1 >= 0) && (i4 >= 0) && faces_limit) { - // try to identify two faces with same normal - very useful if one can create face4 - if (n === 0) norm = new THREE.Vector3(0, 0, 1); else if (n === 30) norm = new THREE.Vector3(0, 0, -1); else { - var norm1 = JSROOT.GEO.GetNormal(vertices[i1], vertices[i1 + 1], vertices[i1 + 2], - vertices[i2], vertices[i2 + 1], vertices[i2 + 2], - vertices[i3], vertices[i3 + 1], vertices[i3 + 2]); - - norm1.normalize(); - - var norm2 = JSROOT.GEO.GetNormal(vertices[i4], vertices[i4 + 1], vertices[i4 + 2], - vertices[i5], vertices[i5 + 1], vertices[i5 + 2], - vertices[i6], vertices[i6 + 1], vertices[i6 + 2]); - - norm2.normalize(); - - if (norm1.distanceToSquared(norm2) < 1e-12) norm = norm1; + // detect same vertices on both Z-layers + for (var side = 0; side < vertices.length; side += vertices.length / 2) + for (var n1 = side; n1 < side + vertices.length / 2 - 3; n1 += 3) + for (var n2 = n1 + 3; n2 < side + vertices.length / 2; n2 += 3) + if ((vertices[n1] === vertices[n2]) && + (vertices[n1 + 1] === vertices[n2 + 1]) && + (vertices[n1 + 2] === vertices[n2 + 2])) { + for (var k = 0; k < indicies.length; ++k) + if (indicies[k] === n2 / 3) indicies[k] = n1 / 3; } - } - if (norm !== null) { - creator.AddFace4(vertices[i1], vertices[i1 + 1], vertices[i1 + 2], + + var map = [], // list of existing faces (with all rotations) + numfaces = 0; + + for (var k = 0; k < indicies.length; k += 3) { + var id1 = indicies[k] * 100 + indicies[k + 1] * 10 + indicies[k + 2], + id2 = indicies[k + 1] * 100 + indicies[k + 2] * 10 + indicies[k], + id3 = indicies[k + 2] * 100 + indicies[k] * 10 + indicies[k + 1]; + + if ((indicies[k] === indicies[k + 1]) || (indicies[k] === indicies[k + 2]) || (indicies[k + 1] === indicies[k + 2]) || + (map.indexOf(id1) >= 0) || (map.indexOf(id2) >= 0) || (map.indexOf(id3) >= 0)) { + indicies[k] = indicies[k + 1] = indicies[k + 2] = -1; + } else { + map.push(id1, id2, id3); + numfaces++; + } + } + + var creator = faces_limit ? new PolygonsCreator : new GeometryCreator(numfaces); + + // var creator = new GeometryCreator(numfaces); + + for (var n = 0; n < indicies.length; n += 6) { + var i1 = indicies[n] * 3, + i2 = indicies[n + 1] * 3, + i3 = indicies[n + 2] * 3, + i4 = indicies[n + 3] * 3, + i5 = indicies[n + 4] * 3, + i6 = indicies[n + 5] * 3, + norm = null; + + if ((i1 >= 0) && (i4 >= 0) && faces_limit) { + // try to identify two faces with same normal - very useful if one can create face4 + if (n === 0) norm = new THREE.Vector3(0, 0, 1); else if (n === 30) norm = new THREE.Vector3(0, 0, -1); else { + var norm1 = GetNormal(vertices[i1], vertices[i1 + 1], vertices[i1 + 2], vertices[i2], vertices[i2 + 1], vertices[i2 + 2], - vertices[i3], vertices[i3 + 1], vertices[i3 + 2], - vertices[i5], vertices[i5 + 1], vertices[i5 + 2]); - creator.SetNormal(norm.x, norm.y, norm.z); - } else { - if (i1 >= 0) { - creator.AddFace3(vertices[i1], vertices[i1 + 1], vertices[i1 + 2], - vertices[i2], vertices[i2 + 1], vertices[i2 + 2], - vertices[i3], vertices[i3 + 1], vertices[i3 + 2]); - creator.CalcNormal(); - } - if (i4 >= 0) { - creator.AddFace3(vertices[i4], vertices[i4 + 1], vertices[i4 + 2], - vertices[i5], vertices[i5 + 1], vertices[i5 + 2], - vertices[i6], vertices[i6 + 1], vertices[i6 + 2]); - creator.CalcNormal(); - } + vertices[i3], vertices[i3 + 1], vertices[i3 + 2]); + + norm1.normalize(); + + var norm2 = GetNormal(vertices[i4], vertices[i4 + 1], vertices[i4 + 2], + vertices[i5], vertices[i5 + 1], vertices[i5 + 2], + vertices[i6], vertices[i6 + 1], vertices[i6 + 2]); + + norm2.normalize(); + + if (norm1.distanceToSquared(norm2) < 1e-12) norm = norm1; } } - return creator.Create(); - }; - - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.createSphereBuffer = function (shape, faces_limit) { - var radius = [shape.fRmax, shape.fRmin], - phiStart = shape.fPhi1, - phiLength = shape.fPhi2 - shape.fPhi1, - thetaStart = shape.fTheta1, - thetaLength = shape.fTheta2 - shape.fTheta1, - widthSegments = shape.fNseg, - heightSegments = shape.fNz, - noInside = (radius[1] <= 0); - - // widthSegments = 20; heightSegments = 10; - // phiStart = 0; phiLength = 360; thetaStart = 0; thetaLength = 180; - - if (faces_limit > 0) { - var fact = (noInside ? 2 : 4) * widthSegments * heightSegments / faces_limit; - - if (fact > 1.) { - widthSegments = Math.max(4, Math.floor(widthSegments / Math.sqrt(fact))); - heightSegments = Math.max(4, Math.floor(heightSegments / Math.sqrt(fact))); - } - } - - var numoutside = widthSegments * heightSegments * 2, - numtop = widthSegments * 2, - numbottom = widthSegments * 2, - numcut = phiLength === 360 ? 0 : heightSegments * (noInside ? 2 : 4), - epsilon = 1e-10; - - if (noInside) numbottom = numtop = widthSegments; - - if (faces_limit < 0) return numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut; - - var _sinp = new Float32Array(widthSegments + 1), - _cosp = new Float32Array(widthSegments + 1), - _sint = new Float32Array(heightSegments + 1), - _cost = new Float32Array(heightSegments + 1); - - for (var n = 0; n <= heightSegments; ++n) { - var theta = (thetaStart + thetaLength / heightSegments * n) * Math.PI / 180; - _sint[n] = Math.sin(theta); - _cost[n] = Math.cos(theta); - } - - for (var n = 0; n <= widthSegments; ++n) { - var phi = (phiStart + phiLength / widthSegments * n) * Math.PI / 180; - _sinp[n] = Math.sin(phi); - _cosp[n] = Math.cos(phi); - } - - if (Math.abs(_sint[0]) <= epsilon) { - numoutside -= widthSegments; - numtop = 0; - } - if (Math.abs(_sint[heightSegments]) <= epsilon) { - numoutside -= widthSegments; - numbottom = 0; - } - - var numfaces = numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut; - - var creator = faces_limit ? new JSROOT.GEO.PolygonsCreator : new JSROOT.GEO.GeometryCreator(numfaces); - - // var creator = new JSROOT.GEO.GeometryCreator(numfaces); - - for (var side = 0; side < 2; ++side) { - if ((side === 1) && noInside) break; - - var r = radius[side], - s = (side === 0) ? 1 : -1, - d1 = 1 - side, d2 = 1 - d1; - - // use direct algorithm for the sphere - here normals and position can be calculated directly - for (var k = 0; k < heightSegments; ++k) { - - var k1 = k + d1, k2 = k + d2; - - var skip = 0; - if (Math.abs(_sint[k1]) <= epsilon) skip = 1; else if (Math.abs(_sint[k2]) <= epsilon) skip = 2; - - for (var n = 0; n < widthSegments; ++n) { - creator.AddFace4( - r * _sint[k1] * _cosp[n], r * _sint[k1] * _sinp[n], r * _cost[k1], - r * _sint[k1] * _cosp[n + 1], r * _sint[k1] * _sinp[n + 1], r * _cost[k1], - r * _sint[k2] * _cosp[n + 1], r * _sint[k2] * _sinp[n + 1], r * _cost[k2], - r * _sint[k2] * _cosp[n], r * _sint[k2] * _sinp[n], r * _cost[k2], - skip); - creator.SetNormal4( - s * _sint[k1] * _cosp[n], s * _sint[k1] * _sinp[n], s * _cost[k1], - s * _sint[k1] * _cosp[n + 1], s * _sint[k1] * _sinp[n + 1], s * _cost[k1], - s * _sint[k2] * _cosp[n + 1], s * _sint[k2] * _sinp[n + 1], s * _cost[k2], - s * _sint[k2] * _cosp[n], s * _sint[k2] * _sinp[n], s * _cost[k2], - skip); - } - } - } - - // top/bottom - for (var side = 0; side <= heightSegments; side += heightSegments) - if (Math.abs(_sint[side]) >= epsilon) { - var ss = _sint[side], cc = _cost[side], - d1 = (side === 0) ? 0 : 1, d2 = 1 - d1; - for (var n = 0; n < widthSegments; ++n) { - creator.AddFace4( - radius[1] * ss * _cosp[n + d1], radius[1] * ss * _sinp[n + d1], radius[1] * cc, - radius[0] * ss * _cosp[n + d1], radius[0] * ss * _sinp[n + d1], radius[0] * cc, - radius[0] * ss * _cosp[n + d2], radius[0] * ss * _sinp[n + d2], radius[0] * cc, - radius[1] * ss * _cosp[n + d2], radius[1] * ss * _sinp[n + d2], radius[1] * cc, - noInside ? 2 : 0); - creator.CalcNormal(); - } - } - - // cut left/right sides - if (phiLength < 360) { - for (var side = 0; side <= widthSegments; side += widthSegments) { - var ss = _sinp[side], cc = _cosp[side], - d1 = (side === 0) ? 1 : 0, d2 = 1 - d1; - - for (var k = 0; k < heightSegments; ++k) { - creator.AddFace4( - radius[1] * _sint[k + d1] * cc, radius[1] * _sint[k + d1] * ss, radius[1] * _cost[k + d1], - radius[0] * _sint[k + d1] * cc, radius[0] * _sint[k + d1] * ss, radius[0] * _cost[k + d1], - radius[0] * _sint[k + d2] * cc, radius[0] * _sint[k + d2] * ss, radius[0] * _cost[k + d2], - radius[1] * _sint[k + d2] * cc, radius[1] * _sint[k + d2] * ss, radius[1] * _cost[k + d2], - noInside ? 2 : 0); - creator.CalcNormal(); - } - } - } - - return creator.Create(); - }; - - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.createTubeBuffer = function (shape, faces_limit) { - var outerR, innerR; // inner/outer tube radius - if ((shape._typename == "TGeoCone") || (shape._typename == "TGeoConeSeg")) { - outerR = [shape.fRmax2, shape.fRmax1]; - innerR = [shape.fRmin2, shape.fRmin1]; + if (norm !== null) { + creator.AddFace4(vertices[i1], vertices[i1 + 1], vertices[i1 + 2], + vertices[i2], vertices[i2 + 1], vertices[i2 + 2], + vertices[i3], vertices[i3 + 1], vertices[i3 + 2], + vertices[i5], vertices[i5 + 1], vertices[i5 + 2]); + creator.SetNormal(norm.x, norm.y, norm.z); } else { - outerR = [shape.fRmax, shape.fRmax]; - innerR = [shape.fRmin, shape.fRmin]; - } - - var hasrmin = (innerR[0] > 0) || (innerR[1] > 0), - thetaStart = 0, thetaLength = 360; - - if ((shape._typename == "TGeoConeSeg") || (shape._typename == "TGeoTubeSeg") || (shape._typename == "TGeoCtub")) { - thetaStart = shape.fPhi1; - thetaLength = shape.fPhi2 - shape.fPhi1; - } - - var radiusSegments = Math.max(4, Math.round(thetaLength / JSROOT.GEO.GradPerSegm)); - - // external surface - var numfaces = radiusSegments * (((outerR[0] <= 0) || (outerR[1] <= 0)) ? 1 : 2); - - // internal surface - if (hasrmin) - numfaces += radiusSegments * (((innerR[0] <= 0) || (innerR[1] <= 0)) ? 1 : 2); - - // upper cap - if (outerR[0] > 0) numfaces += radiusSegments * ((innerR[0] > 0) ? 2 : 1); - // bottom cup - if (outerR[1] > 0) numfaces += radiusSegments * ((innerR[1] > 0) ? 2 : 1); - - if (thetaLength < 360) - numfaces += ((outerR[0] > innerR[0]) ? 2 : 0) + ((outerR[1] > innerR[1]) ? 2 : 0); - - if (faces_limit < 0) return numfaces; - - var phi0 = thetaStart * Math.PI / 180, - dphi = thetaLength / radiusSegments * Math.PI / 180, - _sin = new Float32Array(radiusSegments + 1), - _cos = new Float32Array(radiusSegments + 1); - - for (var seg = 0; seg <= radiusSegments; ++seg) { - _cos[seg] = Math.cos(phi0 + seg * dphi); - _sin[seg] = Math.sin(phi0 + seg * dphi); - } - - var creator = faces_limit ? new JSROOT.GEO.PolygonsCreator : new JSROOT.GEO.GeometryCreator(numfaces); - - // var creator = new JSROOT.GEO.GeometryCreator(numfaces); - - var calcZ; - - if (shape._typename == "TGeoCtub") - calcZ = function (x, y, z) { - var arr = (z < 0) ? shape.fNlow : shape.fNhigh; - return ((z < 0) ? -shape.fDz : shape.fDz) - (x * arr[0] + y * arr[1]) / arr[2]; - }; - - // create outer/inner tube - for (var side = 0; side < 2; ++side) { - if ((side === 1) && !hasrmin) break; - - var R = (side === 0) ? outerR : innerR, - d1 = side, d2 = 1 - side, nxy = 1., nz = 0; - - if (R[0] !== R[1]) { - var angle = Math.atan2((R[1] - R[0]), 2 * shape.fDZ); - nxy = Math.cos(angle); - nz = Math.sin(angle); + if (i1 >= 0) { + creator.AddFace3(vertices[i1], vertices[i1 + 1], vertices[i1 + 2], + vertices[i2], vertices[i2 + 1], vertices[i2 + 2], + vertices[i3], vertices[i3 + 1], vertices[i3 + 2]); + creator.CalcNormal(); } - - if (side === 1) { - nxy *= -1; - nz *= -1; + if (i4 >= 0) { + creator.AddFace3(vertices[i4], vertices[i4 + 1], vertices[i4 + 2], + vertices[i5], vertices[i5 + 1], vertices[i5 + 2], + vertices[i6], vertices[i6 + 1], vertices[i6 + 2]); + creator.CalcNormal(); } - var reduce = 0; - if (R[0] <= 0) reduce = 2; else if (R[1] <= 0) reduce = 1; + } + } - for (var seg = 0; seg < radiusSegments; ++seg) { + return creator.Create(); +} + +/** @memberOf GEO */ +export function createSphereBuffer(shape, faces_limit) { + let radius = [shape.fRmax, shape.fRmin], + phiStart = shape.fPhi1, + phiLength = shape.fPhi2 - shape.fPhi1, + thetaStart = shape.fTheta1, + thetaLength = shape.fTheta2 - shape.fTheta1, + widthSegments = shape.fNseg, + heightSegments = shape.fNz, + noInside = (radius[1] <= 0); + + // widthSegments = 20; heightSegments = 10; + // phiStart = 0; phiLength = 360; thetaStart = 0; thetaLength = 180; + + if (faces_limit > 0) { + var fact = (noInside ? 2 : 4) * widthSegments * heightSegments / faces_limit; + + if (fact > 1.) { + widthSegments = Math.max(4, Math.floor(widthSegments / Math.sqrt(fact))); + heightSegments = Math.max(4, Math.floor(heightSegments / Math.sqrt(fact))); + } + } + + var numoutside = widthSegments * heightSegments * 2, + numtop = widthSegments * 2, + numbottom = widthSegments * 2, + numcut = phiLength === 360 ? 0 : heightSegments * (noInside ? 2 : 4), + epsilon = 1e-10; + + if (noInside) numbottom = numtop = widthSegments; + + if (faces_limit < 0) return numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut; + + var _sinp = new Float32Array(widthSegments + 1), + _cosp = new Float32Array(widthSegments + 1), + _sint = new Float32Array(heightSegments + 1), + _cost = new Float32Array(heightSegments + 1); + + for (var n = 0; n <= heightSegments; ++n) { + var theta = (thetaStart + thetaLength / heightSegments * n) * Math.PI / 180; + _sint[n] = Math.sin(theta); + _cost[n] = Math.cos(theta); + } + + for (var n = 0; n <= widthSegments; ++n) { + var phi = (phiStart + phiLength / widthSegments * n) * Math.PI / 180; + _sinp[n] = Math.sin(phi); + _cosp[n] = Math.cos(phi); + } + + if (Math.abs(_sint[0]) <= epsilon) { + numoutside -= widthSegments; + numtop = 0; + } + if (Math.abs(_sint[heightSegments]) <= epsilon) { + numoutside -= widthSegments; + numbottom = 0; + } + + var numfaces = numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut; + + var creator = faces_limit ? new PolygonsCreator : new GeometryCreator(numfaces); + + // var creator = new GeometryCreator(numfaces); + + for (var side = 0; side < 2; ++side) { + if ((side === 1) && noInside) break; + + var r = radius[side], + s = (side === 0) ? 1 : -1, + d1 = 1 - side, d2 = 1 - d1; + + // use direct algorithm for the sphere - here normals and position can be calculated directly + for (var k = 0; k < heightSegments; ++k) { + + var k1 = k + d1, k2 = k + d2; + + var skip = 0; + if (Math.abs(_sint[k1]) <= epsilon) skip = 1; else if (Math.abs(_sint[k2]) <= epsilon) skip = 2; + + for (var n = 0; n < widthSegments; ++n) { creator.AddFace4( - R[0] * _cos[seg + d1], R[0] * _sin[seg + d1], shape.fDZ, - R[1] * _cos[seg + d1], R[1] * _sin[seg + d1], -shape.fDZ, - R[1] * _cos[seg + d2], R[1] * _sin[seg + d2], -shape.fDZ, - R[0] * _cos[seg + d2], R[0] * _sin[seg + d2], shape.fDZ, - reduce); - - if (calcZ) creator.RecalcZ(calcZ); - - creator.SetNormal_12_34(nxy * _cos[seg + d1], nxy * _sin[seg + d1], nz, - nxy * _cos[seg + d2], nxy * _sin[seg + d2], nz, - reduce); + r * _sint[k1] * _cosp[n], r * _sint[k1] * _sinp[n], r * _cost[k1], + r * _sint[k1] * _cosp[n + 1], r * _sint[k1] * _sinp[n + 1], r * _cost[k1], + r * _sint[k2] * _cosp[n + 1], r * _sint[k2] * _sinp[n + 1], r * _cost[k2], + r * _sint[k2] * _cosp[n], r * _sint[k2] * _sinp[n], r * _cost[k2], + skip); + creator.SetNormal4( + s * _sint[k1] * _cosp[n], s * _sint[k1] * _sinp[n], s * _cost[k1], + s * _sint[k1] * _cosp[n + 1], s * _sint[k1] * _sinp[n + 1], s * _cost[k1], + s * _sint[k2] * _cosp[n + 1], s * _sint[k2] * _sinp[n + 1], s * _cost[k2], + s * _sint[k2] * _cosp[n], s * _sint[k2] * _sinp[n], s * _cost[k2], + skip); } } + } - // create upper/bottom part - for (var side = 0; side < 2; ++side) { - if (outerR[side] <= 0) continue; - - var d1 = side, d2 = 1 - side, - sign = (side == 0) ? 1 : -1, - reduce = (innerR[side] <= 0) ? 2 : 0; - if ((reduce == 2) && (thetaLength === 360) && !calcZ) creator.StartPolygon(side === 0); - for (var seg = 0; seg < radiusSegments; ++seg) { + // top/bottom + for (var side = 0; side <= heightSegments; side += heightSegments) + if (Math.abs(_sint[side]) >= epsilon) { + var ss = _sint[side], cc = _cost[side], + d1 = (side === 0) ? 0 : 1, d2 = 1 - d1; + for (var n = 0; n < widthSegments; ++n) { creator.AddFace4( - innerR[side] * _cos[seg + d1], innerR[side] * _sin[seg + d1], sign * shape.fDZ, - outerR[side] * _cos[seg + d1], outerR[side] * _sin[seg + d1], sign * shape.fDZ, - outerR[side] * _cos[seg + d2], outerR[side] * _sin[seg + d2], sign * shape.fDZ, - innerR[side] * _cos[seg + d2], innerR[side] * _sin[seg + d2], sign * shape.fDZ, - reduce); - if (calcZ) { - creator.RecalcZ(calcZ); - creator.CalcNormal(); - } else { - creator.SetNormal(0, 0, sign); - } - } - - creator.StopPolygon(); - } - - // create cut surfaces - if (thetaLength < 360) { - creator.AddFace4(innerR[1] * _cos[0], innerR[1] * _sin[0], -shape.fDZ, - outerR[1] * _cos[0], outerR[1] * _sin[0], -shape.fDZ, - outerR[0] * _cos[0], outerR[0] * _sin[0], shape.fDZ, - innerR[0] * _cos[0], innerR[0] * _sin[0], shape.fDZ, - (outerR[0] === innerR[0]) ? 2 : ((innerR[1] === outerR[1]) ? 1 : 0)); - if (calcZ) creator.RecalcZ(calcZ); - creator.CalcNormal(); - - creator.AddFace4(innerR[0] * _cos[radiusSegments], innerR[0] * _sin[radiusSegments], shape.fDZ, - outerR[0] * _cos[radiusSegments], outerR[0] * _sin[radiusSegments], shape.fDZ, - outerR[1] * _cos[radiusSegments], outerR[1] * _sin[radiusSegments], -shape.fDZ, - innerR[1] * _cos[radiusSegments], innerR[1] * _sin[radiusSegments], -shape.fDZ, - (outerR[0] === innerR[0]) ? 1 : ((innerR[1] === outerR[1]) ? 2 : 0)); - - if (calcZ) creator.RecalcZ(calcZ); - creator.CalcNormal(); - } - - return creator.Create(); - }; - - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.createEltuBuffer = function (shape, faces_limit) { - var radiusSegments = Math.max(4, Math.round(360 / JSROOT.GEO.GradPerSegm)); - - if (faces_limit < 0) return radiusSegments * 4; - - // calculate all sin/cos tables in advance - var x = new Float32Array(radiusSegments + 1), - y = new Float32Array(radiusSegments + 1); - for (var seg = 0; seg <= radiusSegments; ++seg) { - var phi = seg / radiusSegments * 2 * Math.PI; - x[seg] = shape.fRmin * Math.cos(phi); - y[seg] = shape.fRmax * Math.sin(phi); - } - - var creator = faces_limit ? new JSROOT.GEO.PolygonsCreator : new JSROOT.GEO.GeometryCreator(radiusSegments * 4), - nx1 = 1, ny1 = 0, nx2 = 1, ny2 = 0; - - // create tube faces - for (var seg = 0; seg < radiusSegments; ++seg) { - creator.AddFace4(x[seg], y[seg], +shape.fDZ, - x[seg], y[seg], -shape.fDZ, - x[seg + 1], y[seg + 1], -shape.fDZ, - x[seg + 1], y[seg + 1], shape.fDZ); - - // calculate normals ourself - nx1 = nx2; - ny1 = ny2; - nx2 = x[seg + 1] * shape.fRmax / shape.fRmin; - ny2 = y[seg + 1] * shape.fRmin / shape.fRmax; - var dist = Math.sqrt(nx2 * nx2 + ny2 * ny2); - nx2 = nx2 / dist; - ny2 = ny2 / dist; - - creator.SetNormal_12_34(nx1, ny1, 0, nx2, ny2, 0); - } - - // create top/bottom sides - for (var side = 0; side < 2; ++side) { - var sign = (side === 0) ? 1 : -1, d1 = side, d2 = 1 - side; - for (var seg = 0; seg < radiusSegments; ++seg) { - creator.AddFace3(0, 0, sign * shape.fDZ, - x[seg + d1], y[seg + d1], sign * shape.fDZ, - x[seg + d2], y[seg + d2], sign * shape.fDZ); - creator.SetNormal(0, 0, sign); - } - } - - return creator.Create(); - }; - - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.createTorusBuffer = function (shape, faces_limit) { - var radius = shape.fR, - radialSegments = Math.max(6, Math.round(360 / JSROOT.GEO.GradPerSegm)), - tubularSegments = Math.max(8, Math.round(shape.fDphi / JSROOT.GEO.GradPerSegm)); - - var numfaces = (shape.fRmin > 0 ? 4 : 2) * radialSegments * (tubularSegments + (shape.fDphi !== 360 ? 1 : 0)); - - if (faces_limit < 0) return numfaces; - - if ((faces_limit > 0) && (numfaces > faces_limit)) { - radialSegments = Math.floor(radialSegments / Math.sqrt(numfaces / faces_limit)); - tubularSegments = Math.floor(tubularSegments / Math.sqrt(numfaces / faces_limit)); - numfaces = (shape.fRmin > 0 ? 4 : 2) * radialSegments * (tubularSegments + (shape.fDphi !== 360 ? 1 : 0)); - } - - var _sinr = new Float32Array(radialSegments + 1), - _cosr = new Float32Array(radialSegments + 1), - _sint = new Float32Array(tubularSegments + 1), - _cost = new Float32Array(tubularSegments + 1); - - for (var n = 0; n <= radialSegments; ++n) { - _sinr[n] = Math.sin(n / radialSegments * 2 * Math.PI); - _cosr[n] = Math.cos(n / radialSegments * 2 * Math.PI); - } - - for (var t = 0; t <= tubularSegments; ++t) { - var angle = (shape.fPhi1 + shape.fDphi * t / tubularSegments) / 180 * Math.PI; - _sint[t] = Math.sin(angle); - _cost[t] = Math.cos(angle); - } - - var creator = faces_limit ? new JSROOT.GEO.PolygonsCreator : new JSROOT.GEO.GeometryCreator(numfaces); - - // use vectors for normals calculation - var p1 = new THREE.Vector3(), p2 = new THREE.Vector3(), p3 = new THREE.Vector3(), p4 = new THREE.Vector3(), - n1 = new THREE.Vector3(), n2 = new THREE.Vector3(), n3 = new THREE.Vector3(), n4 = new THREE.Vector3(), - center1 = new THREE.Vector3(), center2 = new THREE.Vector3(); - - for (var side = 0; side < 2; ++side) { - if ((side > 0) && (shape.fRmin <= 0)) break; - var tube = (side > 0) ? shape.fRmin : shape.fRmax, - d1 = 1 - side, d2 = 1 - d1, ns = side > 0 ? -1 : 1; - - for (var t = 0; t < tubularSegments; ++t) { - var t1 = t + d1, t2 = t + d2; - center1.x = radius * _cost[t1]; - center1.y = radius * _sint[t1]; - center2.x = radius * _cost[t2]; - center2.y = radius * _sint[t2]; - - for (var n = 0; n < radialSegments; ++n) { - p1.x = (radius + tube * _cosr[n]) * _cost[t1]; - p1.y = (radius + tube * _cosr[n]) * _sint[t1]; - p1.z = tube * _sinr[n]; - p2.x = (radius + tube * _cosr[n + 1]) * _cost[t1]; - p2.y = (radius + tube * _cosr[n + 1]) * _sint[t1]; - p2.z = tube * _sinr[n + 1]; - p3.x = (radius + tube * _cosr[n + 1]) * _cost[t2]; - p3.y = (radius + tube * _cosr[n + 1]) * _sint[t2]; - p3.z = tube * _sinr[n + 1]; - p4.x = (radius + tube * _cosr[n]) * _cost[t2]; - p4.y = (radius + tube * _cosr[n]) * _sint[t2]; - p4.z = tube * _sinr[n]; - - creator.AddFace4(p1.x, p1.y, p1.z, - p2.x, p2.y, p2.z, - p3.x, p3.y, p3.z, - p4.x, p4.y, p4.z); - - n1.subVectors(p1, center1).normalize(); - n2.subVectors(p2, center1).normalize(); - n3.subVectors(p3, center2).normalize(); - n4.subVectors(p4, center2).normalize(); - - creator.SetNormal4(ns * n1.x, ns * n1.y, ns * n1.z, - ns * n2.x, ns * n2.y, ns * n2.z, - ns * n3.x, ns * n3.y, ns * n3.z, - ns * n4.x, ns * n4.y, ns * n4.z); - } - } - } - - if (shape.fDphi !== 360) - for (var t = 0; t <= tubularSegments; t += tubularSegments) { - var tube1 = shape.fRmax, tube2 = shape.fRmin, - d1 = (t > 0) ? 0 : 1, d2 = 1 - d1, - skip = (shape.fRmin) > 0 ? 0 : 1, - nsign = t > 0 ? 1 : -1; - for (var n = 0; n < radialSegments; ++n) { - creator.AddFace4((radius + tube1 * _cosr[n + d1]) * _cost[t], (radius + tube1 * _cosr[n + d1]) * _sint[t], tube1 * _sinr[n + d1], - (radius + tube2 * _cosr[n + d1]) * _cost[t], (radius + tube2 * _cosr[n + d1]) * _sint[t], tube2 * _sinr[n + d1], - (radius + tube2 * _cosr[n + d2]) * _cost[t], (radius + tube2 * _cosr[n + d2]) * _sint[t], tube2 * _sinr[n + d2], - (radius + tube1 * _cosr[n + d2]) * _cost[t], (radius + tube1 * _cosr[n + d2]) * _sint[t], tube1 * _sinr[n + d2], skip); - creator.SetNormal(-nsign * _sint[t], nsign * _cost[t], 0); - } - } - - return creator.Create(); - }; - - - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.createPolygonBuffer = function (shape, faces_limit) { - var thetaStart = shape.fPhi1, - thetaLength = shape.fDphi, - radiusSegments = 60; - - if (shape._typename == "TGeoPgon") - radiusSegments = shape.fNedges; - else - radiusSegments = Math.max(5, Math.round(thetaLength / JSROOT.GEO.GradPerSegm)); - - var usage = new Int16Array(2 * shape.fNz), numusedlayers = 0, hasrmin = false; - - for (var layer = 0; layer < shape.fNz; ++layer) - if (shape.fRmin[layer] > 0) hasrmin = true; - - // return very rough estimation, number of faces may be much less - if (faces_limit < 0) return (hasrmin ? 4 : 2) * radiusSegments * (shape.fNz - 1); - - // coordinate of point on cut edge (x,z) - var pnts = (thetaLength === 360) ? null : []; - - // first analyse levels - if we need to create all of them - for (var side = 0; side < 2; ++side) { - var rside = (side === 0) ? 'fRmax' : 'fRmin'; - - for (var layer = 0; layer < shape.fNz; ++layer) { - - // first create points for the layer - var layerz = shape.fZ[layer], rad = shape[rside][layer]; - - usage[layer * 2 + side] = 0; - - if ((layer > 0) && (layer < shape.fNz - 1)) - if (((shape.fZ[layer - 1] === layerz) && (shape[rside][layer - 1] === rad)) || - ((shape[rside][layer + 1] === rad) && (shape[rside][layer - 1] === rad))) { - - // same Z and R as before - ignore - // or same R before and after - - continue; - } - - if ((layer > 0) && ((side === 0) || hasrmin)) { - usage[layer * 2 + side] = 1; - numusedlayers++; - } - - if (pnts !== null) { - if (side === 0) { - pnts.push(new THREE.Vector2(rad, layerz)); - } else if (rad < shape.fRmax[layer]) { - pnts.unshift(new THREE.Vector2(rad, layerz)); - } - } - } - } - - var numfaces = numusedlayers * radiusSegments * 2; - if (shape.fRmin[0] !== shape.fRmax[0]) numfaces += radiusSegments * (hasrmin ? 2 : 1); - if (shape.fRmin[shape.fNz - 1] !== shape.fRmax[shape.fNz - 1]) numfaces += radiusSegments * (hasrmin ? 2 : 1); - - var cut_faces = null; - - if (pnts !== null) { - if (pnts.length === shape.fNz * 2) { - // special case - all layers are there, create faces ourself - cut_faces = []; - for (var layer = shape.fNz - 1; layer > 0; --layer) { - if (shape.fZ[layer] === shape.fZ[layer - 1]) continue; - var right = 2 * shape.fNz - 1 - layer; - cut_faces.push([right, layer - 1, layer]); - cut_faces.push([right, right + 1, layer - 1]); - } - - } else { - // let three.js calculate our faces - // console.log('triangulate polygon ' + shape.fShapeId); - cut_faces = THREE.ShapeUtils.triangulateShape(pnts, []); - } - numfaces += cut_faces.length * 2; - } - - var phi0 = thetaStart * Math.PI / 180, dphi = thetaLength / radiusSegments * Math.PI / 180; - - // calculate all sin/cos tables in advance - var _sin = new Float32Array(radiusSegments + 1), - _cos = new Float32Array(radiusSegments + 1); - for (let seg = 0; seg <= radiusSegments; ++seg) { - _cos[seg] = Math.cos(phi0 + seg * dphi); - _sin[seg] = Math.sin(phi0 + seg * dphi); - } - - var creator = faces_limit ? new JSROOT.GEO.PolygonsCreator : new JSROOT.GEO.GeometryCreator(numfaces); - - // add sides - for (let side = 0; side < 2; ++side) { - var rside = (side === 0) ? 'fRmax' : 'fRmin', - z1 = shape.fZ[0], r1 = shape[rside][0], - d1 = 1 - side, d2 = side; - - for (var layer = 0; layer < shape.fNz; ++layer) { - - if (usage[layer * 2 + side] === 0) continue; - - var z2 = shape.fZ[layer], r2 = shape[rside][layer], - nxy = 1, nz = 0; - - if ((r2 !== r1)) { - var angle = Math.atan2((r2 - r1), (z2 - z1)); - nxy = Math.cos(angle); - nz = Math.sin(angle); - } - - if (side > 0) { - nxy *= -1; - nz *= -1; - } - - for (var seg = 0; seg < radiusSegments; ++seg) { - creator.AddFace4(r1 * _cos[seg + d1], r1 * _sin[seg + d1], z1, - r2 * _cos[seg + d1], r2 * _sin[seg + d1], z2, - r2 * _cos[seg + d2], r2 * _sin[seg + d2], z2, - r1 * _cos[seg + d2], r1 * _sin[seg + d2], z1); - creator.SetNormal_12_34(nxy * _cos[seg + d1], nxy * _sin[seg + d1], nz, nxy * _cos[seg + d2], nxy * _sin[seg + d2], nz); - } - - z1 = z2; - r1 = r2; - } - } - - // add top/bottom - for (var layer = 0; layer < shape.fNz; layer += (shape.fNz - 1)) { - - var rmin = shape.fRmin[layer], rmax = shape.fRmax[layer]; - - if (rmin === rmax) continue; - - var layerz = shape.fZ[layer], - d1 = (layer === 0) ? 1 : 0, d2 = 1 - d1, - normalz = (layer === 0) ? -1 : 1; - - if (!hasrmin && !cut_faces) creator.StartPolygon(layer > 0); - - for (var seg = 0; seg < radiusSegments; ++seg) { - creator.AddFace4(rmin * _cos[seg + d1], rmin * _sin[seg + d1], layerz, - rmax * _cos[seg + d1], rmax * _sin[seg + d1], layerz, - rmax * _cos[seg + d2], rmax * _sin[seg + d2], layerz, - rmin * _cos[seg + d2], rmin * _sin[seg + d2], layerz, - hasrmin ? 0 : 2); - creator.SetNormal(0, 0, normalz); - } - - creator.StopPolygon(); - } - - if (cut_faces) - for (var seg = 0; seg <= radiusSegments; seg += radiusSegments) { - var d1 = (seg === 0) ? 1 : 2, d2 = 3 - d1; - for (var n = 0; n < cut_faces.length; ++n) { - var a = pnts[cut_faces[n][0]], - b = pnts[cut_faces[n][d1]], - c = pnts[cut_faces[n][d2]]; - - creator.AddFace3(a.x * _cos[seg], a.x * _sin[seg], a.y, - b.x * _cos[seg], b.x * _sin[seg], b.y, - c.x * _cos[seg], c.x * _sin[seg], c.y); - - creator.CalcNormal(); - } - } - - return creator.Create(); - }; - - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.createXtruBuffer = function (shape, faces_limit) { - var nfaces = (shape.fNz - 1) * shape.fNvert * 2; - - if (faces_limit < 0) return nfaces + shape.fNvert * 3; - - // create points - var pnts = []; - for (var vert = 0; vert < shape.fNvert; ++vert) - pnts.push(new THREE.Vector2(shape.fX[vert], shape.fY[vert])); - - // console.log('triangulate Xtru ' + shape.fShapeId); - var faces = THREE.ShapeUtils.triangulateShape(pnts, []); - if (faces.length < pnts.length - 2) { - JSROOT.GEO.warn('Problem with XTRU shape ' + shape.fName + ' with ' + pnts.length + ' vertices'); - faces = []; - } else { - nfaces += faces.length * 2; - } - - var creator = faces_limit ? new JSROOT.GEO.PolygonsCreator : new JSROOT.GEO.GeometryCreator(nfaces); - - for (var layer = 0; layer < shape.fNz - 1; ++layer) { - var z1 = shape.fZ[layer], scale1 = shape.fScale[layer], - z2 = shape.fZ[layer + 1], scale2 = shape.fScale[layer + 1]; - - for (var vert1 = 0; vert1 < shape.fNvert; ++vert1) { - var vert2 = (vert1 + 1) % shape.fNvert; - creator.AddFace4(scale1 * shape.fX[vert1], scale1 * shape.fY[vert1], z1, - scale2 * shape.fX[vert1], scale2 * shape.fY[vert1], z2, - scale2 * shape.fX[vert2], scale2 * shape.fY[vert2], z2, - scale1 * shape.fX[vert2], scale1 * shape.fY[vert2], z1); + radius[1] * ss * _cosp[n + d1], radius[1] * ss * _sinp[n + d1], radius[1] * cc, + radius[0] * ss * _cosp[n + d1], radius[0] * ss * _sinp[n + d1], radius[0] * cc, + radius[0] * ss * _cosp[n + d2], radius[0] * ss * _sinp[n + d2], radius[0] * cc, + radius[1] * ss * _cosp[n + d2], radius[1] * ss * _sinp[n + d2], radius[1] * cc, + noInside ? 2 : 0); creator.CalcNormal(); } } - for (layer = 0; layer <= shape.fNz - 1; layer += (shape.fNz - 1)) { - var z = shape.fZ[layer], scale = shape.fScale[layer]; + // cut left/right sides + if (phiLength < 360) { + for (var side = 0; side <= widthSegments; side += widthSegments) { + var ss = _sinp[side], cc = _cosp[side], + d1 = (side === 0) ? 1 : 0, d2 = 1 - d1; - for (var n = 0; n < faces.length; ++n) { - var face = faces[n], - pnt1 = pnts[face[0]], - pnt2 = pnts[face[(layer === 0) ? 2 : 1]], - pnt3 = pnts[face[(layer === 0) ? 1 : 2]]; + for (var k = 0; k < heightSegments; ++k) { + creator.AddFace4( + radius[1] * _sint[k + d1] * cc, radius[1] * _sint[k + d1] * ss, radius[1] * _cost[k + d1], + radius[0] * _sint[k + d1] * cc, radius[0] * _sint[k + d1] * ss, radius[0] * _cost[k + d1], + radius[0] * _sint[k + d2] * cc, radius[0] * _sint[k + d2] * ss, radius[0] * _cost[k + d2], + radius[1] * _sint[k + d2] * cc, radius[1] * _sint[k + d2] * ss, radius[1] * _cost[k + d2], + noInside ? 2 : 0); + creator.CalcNormal(); + } + } + } - creator.AddFace3(scale * pnt1.x, scale * pnt1.y, z, - scale * pnt2.x, scale * pnt2.y, z, - scale * pnt3.x, scale * pnt3.y, z); - creator.SetNormal(0, 0, layer === 0 ? -1 : 1); + return creator.Create(); +} + +/** @memberOf GEO */ +export function createTubeBuffer(shape, faces_limit) { + var outerR, innerR; // inner/outer tube radius + if ((shape._typename === "TGeoCone") || (shape._typename === "TGeoConeSeg")) { + outerR = [shape.fRmax2, shape.fRmax1]; + innerR = [shape.fRmin2, shape.fRmin1]; + } else { + outerR = [shape.fRmax, shape.fRmax]; + innerR = [shape.fRmin, shape.fRmin]; + } + + var hasrmin = (innerR[0] > 0) || (innerR[1] > 0), + thetaStart = 0, thetaLength = 360; + + if ((shape._typename === "TGeoConeSeg") || (shape._typename === "TGeoTubeSeg") || (shape._typename === "TGeoCtub")) { + thetaStart = shape.fPhi1; + thetaLength = shape.fPhi2 - shape.fPhi1; + } + + var radiusSegments = Math.max(4, Math.round(thetaLength / GradPerSegm)); + + // external surface + var numfaces = radiusSegments * (((outerR[0] <= 0) || (outerR[1] <= 0)) ? 1 : 2); + + // internal surface + if (hasrmin) + numfaces += radiusSegments * (((innerR[0] <= 0) || (innerR[1] <= 0)) ? 1 : 2); + + // upper cap + if (outerR[0] > 0) numfaces += radiusSegments * ((innerR[0] > 0) ? 2 : 1); + // bottom cup + if (outerR[1] > 0) numfaces += radiusSegments * ((innerR[1] > 0) ? 2 : 1); + + if (thetaLength < 360) + numfaces += ((outerR[0] > innerR[0]) ? 2 : 0) + ((outerR[1] > innerR[1]) ? 2 : 0); + + if (faces_limit < 0) return numfaces; + + var phi0 = thetaStart * Math.PI / 180, + dphi = thetaLength / radiusSegments * Math.PI / 180, + _sin = new Float32Array(radiusSegments + 1), + _cos = new Float32Array(radiusSegments + 1); + + for (var seg = 0; seg <= radiusSegments; ++seg) { + _cos[seg] = Math.cos(phi0 + seg * dphi); + _sin[seg] = Math.sin(phi0 + seg * dphi); + } + + var creator = faces_limit ? new PolygonsCreator : new GeometryCreator(numfaces); + + // var creator = new GeometryCreator(numfaces); + + var calcZ; + + if (shape._typename === "TGeoCtub") + calcZ = function (x, y, z) { + var arr = (z < 0) ? shape.fNlow : shape.fNhigh; + return ((z < 0) ? -shape.fDz : shape.fDz) - (x * arr[0] + y * arr[1]) / arr[2]; + }; + + // create outer/inner tube + for (var side = 0; side < 2; ++side) { + if ((side === 1) && !hasrmin) break; + + var R = (side === 0) ? outerR : innerR, + d1 = side, d2 = 1 - side, nxy = 1., nz = 0; + + if (R[0] !== R[1]) { + var angle = Math.atan2((R[1] - R[0]), 2 * shape.fDZ); + nxy = Math.cos(angle); + nz = Math.sin(angle); + } + + if (side === 1) { + nxy *= -1; + nz *= -1; + } + var reduce = 0; + if (R[0] <= 0) reduce = 2; else if (R[1] <= 0) reduce = 1; + + for (var seg = 0; seg < radiusSegments; ++seg) { + creator.AddFace4( + R[0] * _cos[seg + d1], R[0] * _sin[seg + d1], shape.fDZ, + R[1] * _cos[seg + d1], R[1] * _sin[seg + d1], -shape.fDZ, + R[1] * _cos[seg + d2], R[1] * _sin[seg + d2], -shape.fDZ, + R[0] * _cos[seg + d2], R[0] * _sin[seg + d2], shape.fDZ, + reduce); + + if (calcZ) creator.RecalcZ(calcZ); + + creator.SetNormal_12_34(nxy * _cos[seg + d1], nxy * _sin[seg + d1], nz, + nxy * _cos[seg + d2], nxy * _sin[seg + d2], nz, + reduce); + } + } + + // create upper/bottom part + for (var side = 0; side < 2; ++side) { + if (outerR[side] <= 0) continue; + + var d1 = side, d2 = 1 - side, + sign = (side === 0) ? 1 : -1, + reduce = (innerR[side] <= 0) ? 2 : 0; + if ((reduce === 2) && (thetaLength === 360) && !calcZ) creator.StartPolygon(side === 0); + for (var seg = 0; seg < radiusSegments; ++seg) { + creator.AddFace4( + innerR[side] * _cos[seg + d1], innerR[side] * _sin[seg + d1], sign * shape.fDZ, + outerR[side] * _cos[seg + d1], outerR[side] * _sin[seg + d1], sign * shape.fDZ, + outerR[side] * _cos[seg + d2], outerR[side] * _sin[seg + d2], sign * shape.fDZ, + innerR[side] * _cos[seg + d2], innerR[side] * _sin[seg + d2], sign * shape.fDZ, + reduce); + if (calcZ) { + creator.RecalcZ(calcZ); + creator.CalcNormal(); + } else { + creator.SetNormal(0, 0, sign); } } - return creator.Create(); - }; + creator.StopPolygon(); + } - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.createParaboloidBuffer = function (shape, faces_limit) { + // create cut surfaces + if (thetaLength < 360) { + creator.AddFace4(innerR[1] * _cos[0], innerR[1] * _sin[0], -shape.fDZ, + outerR[1] * _cos[0], outerR[1] * _sin[0], -shape.fDZ, + outerR[0] * _cos[0], outerR[0] * _sin[0], shape.fDZ, + innerR[0] * _cos[0], innerR[0] * _sin[0], shape.fDZ, + (outerR[0] === innerR[0]) ? 2 : ((innerR[1] === outerR[1]) ? 1 : 0)); + if (calcZ) creator.RecalcZ(calcZ); + creator.CalcNormal(); - var radiusSegments = Math.max(4, Math.round(360 / JSROOT.GEO.GradPerSegm)), - heightSegments = 30; + creator.AddFace4(innerR[0] * _cos[radiusSegments], innerR[0] * _sin[radiusSegments], shape.fDZ, + outerR[0] * _cos[radiusSegments], outerR[0] * _sin[radiusSegments], shape.fDZ, + outerR[1] * _cos[radiusSegments], outerR[1] * _sin[radiusSegments], -shape.fDZ, + innerR[1] * _cos[radiusSegments], innerR[1] * _sin[radiusSegments], -shape.fDZ, + (outerR[0] === innerR[0]) ? 1 : ((innerR[1] === outerR[1]) ? 2 : 0)); - if (faces_limit > 0) { - var fact = 2 * radiusSegments * (heightSegments + 1) / faces_limit; - if (fact > 1.) { - radiusSegments = Math.max(5, Math.floor(radiusSegments / Math.sqrt(fact))); - heightSegments = Math.max(5, Math.floor(heightSegments / Math.sqrt(fact))); + if (calcZ) creator.RecalcZ(calcZ); + creator.CalcNormal(); + } + + return creator.Create(); +} + +/** @memberOf GEO */ +export function createEltuBuffer(shape, faces_limit) { + var radiusSegments = Math.max(4, Math.round(360 / GradPerSegm)); + + if (faces_limit < 0) return radiusSegments * 4; + + // calculate all sin/cos tables in advance + var x = new Float32Array(radiusSegments + 1), + y = new Float32Array(radiusSegments + 1); + for (var seg = 0; seg <= radiusSegments; ++seg) { + var phi = seg / radiusSegments * 2 * Math.PI; + x[seg] = shape.fRmin * Math.cos(phi); + y[seg] = shape.fRmax * Math.sin(phi); + } + + var creator = faces_limit ? new PolygonsCreator : new GeometryCreator(radiusSegments * 4), + nx1 = 1, ny1 = 0, nx2 = 1, ny2 = 0; + + // create tube faces + for (var seg = 0; seg < radiusSegments; ++seg) { + creator.AddFace4(x[seg], y[seg], +shape.fDZ, + x[seg], y[seg], -shape.fDZ, + x[seg + 1], y[seg + 1], -shape.fDZ, + x[seg + 1], y[seg + 1], shape.fDZ); + + // calculate normals ourself + nx1 = nx2; + ny1 = ny2; + nx2 = x[seg + 1] * shape.fRmax / shape.fRmin; + ny2 = y[seg + 1] * shape.fRmin / shape.fRmax; + var dist = Math.sqrt(nx2 * nx2 + ny2 * ny2); + nx2 = nx2 / dist; + ny2 = ny2 / dist; + + creator.SetNormal_12_34(nx1, ny1, 0, nx2, ny2, 0); + } + + // create top/bottom sides + for (var side = 0; side < 2; ++side) { + var sign = (side === 0) ? 1 : -1, d1 = side, d2 = 1 - side; + for (var seg = 0; seg < radiusSegments; ++seg) { + creator.AddFace3(0, 0, sign * shape.fDZ, + x[seg + d1], y[seg + d1], sign * shape.fDZ, + x[seg + d2], y[seg + d2], sign * shape.fDZ); + creator.SetNormal(0, 0, sign); + } + } + + return creator.Create(); +} + +/** @memberOf GEO */ +export function createTorusBuffer(shape, faces_limit) { + var radius = shape.fR, + radialSegments = Math.max(6, Math.round(360 / GradPerSegm)), + tubularSegments = Math.max(8, Math.round(shape.fDphi / GradPerSegm)); + + var numfaces = (shape.fRmin > 0 ? 4 : 2) * radialSegments * (tubularSegments + (shape.fDphi !== 360 ? 1 : 0)); + + if (faces_limit < 0) return numfaces; + + if ((faces_limit > 0) && (numfaces > faces_limit)) { + radialSegments = Math.floor(radialSegments / Math.sqrt(numfaces / faces_limit)); + tubularSegments = Math.floor(tubularSegments / Math.sqrt(numfaces / faces_limit)); + numfaces = (shape.fRmin > 0 ? 4 : 2) * radialSegments * (tubularSegments + (shape.fDphi !== 360 ? 1 : 0)); + } + + var _sinr = new Float32Array(radialSegments + 1), + _cosr = new Float32Array(radialSegments + 1), + _sint = new Float32Array(tubularSegments + 1), + _cost = new Float32Array(tubularSegments + 1); + + for (var n = 0; n <= radialSegments; ++n) { + _sinr[n] = Math.sin(n / radialSegments * 2 * Math.PI); + _cosr[n] = Math.cos(n / radialSegments * 2 * Math.PI); + } + + for (var t = 0; t <= tubularSegments; ++t) { + var angle = (shape.fPhi1 + shape.fDphi * t / tubularSegments) / 180 * Math.PI; + _sint[t] = Math.sin(angle); + _cost[t] = Math.cos(angle); + } + + var creator = faces_limit ? new PolygonsCreator : new GeometryCreator(numfaces); + + // use vectors for normals calculation + var p1 = new THREE.Vector3(), p2 = new THREE.Vector3(), p3 = new THREE.Vector3(), p4 = new THREE.Vector3(), + n1 = new THREE.Vector3(), n2 = new THREE.Vector3(), n3 = new THREE.Vector3(), n4 = new THREE.Vector3(), + center1 = new THREE.Vector3(), center2 = new THREE.Vector3(); + + for (var side = 0; side < 2; ++side) { + if ((side > 0) && (shape.fRmin <= 0)) break; + var tube = (side > 0) ? shape.fRmin : shape.fRmax, + d1 = 1 - side, d2 = 1 - d1, ns = side > 0 ? -1 : 1; + + for (var t = 0; t < tubularSegments; ++t) { + var t1 = t + d1, t2 = t + d2; + center1.x = radius * _cost[t1]; + center1.y = radius * _sint[t1]; + center2.x = radius * _cost[t2]; + center2.y = radius * _sint[t2]; + + for (var n = 0; n < radialSegments; ++n) { + p1.x = (radius + tube * _cosr[n]) * _cost[t1]; + p1.y = (radius + tube * _cosr[n]) * _sint[t1]; + p1.z = tube * _sinr[n]; + p2.x = (radius + tube * _cosr[n + 1]) * _cost[t1]; + p2.y = (radius + tube * _cosr[n + 1]) * _sint[t1]; + p2.z = tube * _sinr[n + 1]; + p3.x = (radius + tube * _cosr[n + 1]) * _cost[t2]; + p3.y = (radius + tube * _cosr[n + 1]) * _sint[t2]; + p3.z = tube * _sinr[n + 1]; + p4.x = (radius + tube * _cosr[n]) * _cost[t2]; + p4.y = (radius + tube * _cosr[n]) * _sint[t2]; + p4.z = tube * _sinr[n]; + + creator.AddFace4(p1.x, p1.y, p1.z, + p2.x, p2.y, p2.z, + p3.x, p3.y, p3.z, + p4.x, p4.y, p4.z); + + n1.subVectors(p1, center1).normalize(); + n2.subVectors(p2, center1).normalize(); + n3.subVectors(p3, center2).normalize(); + n4.subVectors(p4, center2).normalize(); + + creator.SetNormal4(ns * n1.x, ns * n1.y, ns * n1.z, + ns * n2.x, ns * n2.y, ns * n2.z, + ns * n3.x, ns * n3.y, ns * n3.z, + ns * n4.x, ns * n4.y, ns * n4.z); + } + } + } + + if (shape.fDphi !== 360) + for (var t = 0; t <= tubularSegments; t += tubularSegments) { + var tube1 = shape.fRmax, tube2 = shape.fRmin, + d1 = (t > 0) ? 0 : 1, d2 = 1 - d1, + skip = (shape.fRmin) > 0 ? 0 : 1, + nsign = t > 0 ? 1 : -1; + for (var n = 0; n < radialSegments; ++n) { + creator.AddFace4((radius + tube1 * _cosr[n + d1]) * _cost[t], (radius + tube1 * _cosr[n + d1]) * _sint[t], tube1 * _sinr[n + d1], + (radius + tube2 * _cosr[n + d1]) * _cost[t], (radius + tube2 * _cosr[n + d1]) * _sint[t], tube2 * _sinr[n + d1], + (radius + tube2 * _cosr[n + d2]) * _cost[t], (radius + tube2 * _cosr[n + d2]) * _sint[t], tube2 * _sinr[n + d2], + (radius + tube1 * _cosr[n + d2]) * _cost[t], (radius + tube1 * _cosr[n + d2]) * _sint[t], tube1 * _sinr[n + d2], skip); + creator.SetNormal(-nsign * _sint[t], nsign * _cost[t], 0); } } - var zmin = -shape.fDZ, zmax = shape.fDZ, rmin = shape.fRlo, rmax = shape.fRhi; + return creator.Create(); +} + +/** @memberOf GEO */ +export function createPolygonBuffer(shape, faces_limit) { + var thetaStart = shape.fPhi1, + thetaLength = shape.fDphi, + radiusSegments = 60; + + if (shape._typename === "TGeoPgon") + radiusSegments = shape.fNedges; + else + radiusSegments = Math.max(5, Math.round(thetaLength / GradPerSegm)); + + var usage = new Int16Array(2 * shape.fNz), numusedlayers = 0, hasrmin = false; + + for (var layer = 0; layer < shape.fNz; ++layer) + if (shape.fRmin[layer] > 0) hasrmin = true; + + // return very rough estimation, number of faces may be much less + if (faces_limit < 0) return (hasrmin ? 4 : 2) * radiusSegments * (shape.fNz - 1); + + // coordinate of point on cut edge (x,z) + var pnts = (thetaLength === 360) ? null : []; + + // first analyse levels - if we need to create all of them + for (var side = 0; side < 2; ++side) { + var rside = (side === 0) ? 'fRmax' : 'fRmin'; + + for (var layer = 0; layer < shape.fNz; ++layer) { + + // first create points for the layer + var layerz = shape.fZ[layer], rad = shape[rside][layer]; + + usage[layer * 2 + side] = 0; + + if ((layer > 0) && (layer < shape.fNz - 1)) + if (((shape.fZ[layer - 1] === layerz) && (shape[rside][layer - 1] === rad)) || + ((shape[rside][layer + 1] === rad) && (shape[rside][layer - 1] === rad))) { + + // same Z and R as before - ignore + // or same R before and after + + continue; + } + + if ((layer > 0) && ((side === 0) || hasrmin)) { + usage[layer * 2 + side] = 1; + numusedlayers++; + } + + if (pnts !== null) { + if (side === 0) { + pnts.push(new THREE.Vector2(rad, layerz)); + } else if (rad < shape.fRmax[layer]) { + pnts.unshift(new THREE.Vector2(rad, layerz)); + } + } + } + } + + var numfaces = numusedlayers * radiusSegments * 2; + if (shape.fRmin[0] !== shape.fRmax[0]) numfaces += radiusSegments * (hasrmin ? 2 : 1); + if (shape.fRmin[shape.fNz - 1] !== shape.fRmax[shape.fNz - 1]) numfaces += radiusSegments * (hasrmin ? 2 : 1); + + var cut_faces = null; + + if (pnts !== null) { + if (pnts.length === shape.fNz * 2) { + // special case - all layers are there, create faces ourself + cut_faces = []; + for (var layer = shape.fNz - 1; layer > 0; --layer) { + if (shape.fZ[layer] === shape.fZ[layer - 1]) continue; + var right = 2 * shape.fNz - 1 - layer; + cut_faces.push([right, layer - 1, layer]); + cut_faces.push([right, right + 1, layer - 1]); + } - // if no radius at -z, find intersection - if (shape.fA >= 0) { - if (shape.fB > zmin) zmin = shape.fB; } else { - if (shape.fB < zmax) zmax = shape.fB; + // let three.js calculate our faces + // console.log('triangulate polygon ' + shape.fShapeId); + cut_faces = THREE.ShapeUtils.triangulateShape(pnts, []); } + numfaces += cut_faces.length * 2; + } - var ttmin = Math.atan2(zmin, rmin), ttmax = Math.atan2(zmax, rmax); + var phi0 = thetaStart * Math.PI / 180, dphi = thetaLength / radiusSegments * Math.PI / 180; - var numfaces = (heightSegments + 1) * radiusSegments * 2; - if (rmin === 0) numfaces -= radiusSegments * 2; // complete layer - if (rmax === 0) numfaces -= radiusSegments * 2; // complete layer + // calculate all sin/cos tables in advance + var _sin = new Float32Array(radiusSegments + 1), + _cos = new Float32Array(radiusSegments + 1); + for (let seg = 0; seg <= radiusSegments; ++seg) { + _cos[seg] = Math.cos(phi0 + seg * dphi); + _sin[seg] = Math.sin(phi0 + seg * dphi); + } - if (faces_limit < 0) return numfaces; + var creator = faces_limit ? new PolygonsCreator : new GeometryCreator(numfaces); - // calculate all sin/cos tables in advance - var _sin = new Float32Array(radiusSegments + 1), - _cos = new Float32Array(radiusSegments + 1); - for (var seg = 0; seg <= radiusSegments; ++seg) { - _cos[seg] = Math.cos(seg / radiusSegments * 2 * Math.PI); - _sin[seg] = Math.sin(seg / radiusSegments * 2 * Math.PI); - } + // add sides + for (let side = 0; side < 2; ++side) { + var rside = (side === 0) ? 'fRmax' : 'fRmin', + z1 = shape.fZ[0], r1 = shape[rside][0], + d1 = 1 - side, d2 = side; - var creator = faces_limit ? new JSROOT.GEO.PolygonsCreator : new JSROOT.GEO.GeometryCreator(numfaces); + for (var layer = 0; layer < shape.fNz; ++layer) { - var lastz = zmin, lastr = 0, lastnxy = 0, lastnz = -1; + if (usage[layer * 2 + side] === 0) continue; - for (var layer = 0; layer <= heightSegments + 1; ++layer) { + var z2 = shape.fZ[layer], r2 = shape[rside][layer], + nxy = 1, nz = 0; - var layerz = 0, radius = 0, nxy = 0, nz = -1; - - if ((layer === 0) && (rmin === 0)) continue; - - if ((layer === heightSegments + 1) && (lastr === 0)) break; - - switch (layer) { - case 0: - layerz = zmin; - radius = rmin; - break; - case heightSegments: - layerz = zmax; - radius = rmax; - break; - case heightSegments + 1: - layerz = zmax; - radius = 0; - break; - default: { - var tt = Math.tan(ttmin + (ttmax - ttmin) * layer / heightSegments); - var delta = tt * tt - 4 * shape.fA * shape.fB; // should be always positive (a*b<0) - radius = 0.5 * (tt + Math.sqrt(delta)) / shape.fA; - if (radius < 1e-6) radius = 0; - layerz = radius * tt; - } + if ((r2 !== r1)) { + var angle = Math.atan2((r2 - r1), (z2 - z1)); + nxy = Math.cos(angle); + nz = Math.sin(angle); } - nxy = shape.fA * radius; - nz = (shape.fA > 0) ? -1 : 1; - - var skip = 0; - if (lastr === 0) skip = 1; else if (radius === 0) skip = 2; + if (side > 0) { + nxy *= -1; + nz *= -1; + } for (var seg = 0; seg < radiusSegments; ++seg) { - creator.AddFace4(radius * _cos[seg], radius * _sin[seg], layerz, - lastr * _cos[seg], lastr * _sin[seg], lastz, - lastr * _cos[seg + 1], lastr * _sin[seg + 1], lastz, - radius * _cos[seg + 1], radius * _sin[seg + 1], layerz, skip); - - // use analytic normal values when open/closing paraboloid around 0 - // cut faces (top or bottom) set with simple normal - if ((skip === 0) || ((layer === 1) && (rmin === 0)) || ((layer === heightSegments + 1) && (rmax === 0))) - creator.SetNormal4(nxy * _cos[seg], nxy * _sin[seg], nz, - lastnxy * _cos[seg], lastnxy * _sin[seg], lastnz, - lastnxy * _cos[seg + 1], lastnxy * _sin[seg + 1], lastnz, - nxy * _cos[seg + 1], nxy * _sin[seg + 1], nz, skip); - else - creator.SetNormal(0, 0, (layer < heightSegments) ? -1 : 1); + creator.AddFace4(r1 * _cos[seg + d1], r1 * _sin[seg + d1], z1, + r2 * _cos[seg + d1], r2 * _sin[seg + d1], z2, + r2 * _cos[seg + d2], r2 * _sin[seg + d2], z2, + r1 * _cos[seg + d2], r1 * _sin[seg + d2], z1); + creator.SetNormal_12_34(nxy * _cos[seg + d1], nxy * _sin[seg + d1], nz, nxy * _cos[seg + d2], nxy * _sin[seg + d2], nz); } - lastz = layerz; - lastr = radius; - lastnxy = nxy; - lastnz = nz; + z1 = z2; + r1 = r2; + } + } + + // add top/bottom + for (var layer = 0; layer < shape.fNz; layer += (shape.fNz - 1)) { + + var rmin = shape.fRmin[layer], rmax = shape.fRmax[layer]; + + if (rmin === rmax) continue; + + var layerz = shape.fZ[layer], + d1 = (layer === 0) ? 1 : 0, d2 = 1 - d1, + normalz = (layer === 0) ? -1 : 1; + + if (!hasrmin && !cut_faces) creator.StartPolygon(layer > 0); + + for (var seg = 0; seg < radiusSegments; ++seg) { + creator.AddFace4(rmin * _cos[seg + d1], rmin * _sin[seg + d1], layerz, + rmax * _cos[seg + d1], rmax * _sin[seg + d1], layerz, + rmax * _cos[seg + d2], rmax * _sin[seg + d2], layerz, + rmin * _cos[seg + d2], rmin * _sin[seg + d2], layerz, + hasrmin ? 0 : 2); + creator.SetNormal(0, 0, normalz); } - return creator.Create(); - }; + creator.StopPolygon(); + } - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.createHypeBuffer = function (shape, faces_limit) { + if (cut_faces) + for (var seg = 0; seg <= radiusSegments; seg += radiusSegments) { + var d1 = (seg === 0) ? 1 : 2, d2 = 3 - d1; + for (var n = 0; n < cut_faces.length; ++n) { + var a = pnts[cut_faces[n][0]], + b = pnts[cut_faces[n][d1]], + c = pnts[cut_faces[n][d2]]; - if ((shape.fTin === 0) && (shape.fTout === 0)) - return JSROOT.GEO.createTubeBuffer(shape, faces_limit); + creator.AddFace3(a.x * _cos[seg], a.x * _sin[seg], a.y, + b.x * _cos[seg], b.x * _sin[seg], b.y, + c.x * _cos[seg], c.x * _sin[seg], c.y); - var radiusSegments = Math.max(4, Math.round(360 / JSROOT.GEO.GradPerSegm)), - heightSegments = 30; - - var numfaces = radiusSegments * (heightSegments + 1) * ((shape.fRmin > 0) ? 4 : 2); - - if (faces_limit < 0) return numfaces; - - if ((faces_limit > 0) && (faces_limit > numfaces)) { - radiusSegments = Math.max(4, Math.floor(radiusSegments / Math.sqrt(numfaces / faces_limit))); - heightSegments = Math.max(4, Math.floor(heightSegments / Math.sqrt(numfaces / faces_limit))); - numfaces = radiusSegments * (heightSegments + 1) * ((shape.fRmin > 0) ? 4 : 2); - } - - // calculate all sin/cos tables in advance - var _sin = new Float32Array(radiusSegments + 1), _cos = new Float32Array(radiusSegments + 1); - for (var seg = 0; seg <= radiusSegments; ++seg) { - _cos[seg] = Math.cos(seg / radiusSegments * 2 * Math.PI); - _sin[seg] = Math.sin(seg / radiusSegments * 2 * Math.PI); - } - - var creator = faces_limit ? new JSROOT.GEO.PolygonsCreator : new JSROOT.GEO.GeometryCreator(numfaces); - - // in-out side - for (var side = 0; side < 2; ++side) { - if ((side > 0) && (shape.fRmin <= 0)) break; - - var r0 = (side > 0) ? shape.fRmin : shape.fRmax, - tsq = (side > 0) ? shape.fTinsq : shape.fToutsq, - d1 = 1 - side, d2 = 1 - d1; - - // vertical layers - for (var layer = 0; layer < heightSegments; ++layer) { - var z1 = -shape.fDz + layer / heightSegments * 2 * shape.fDz, - z2 = -shape.fDz + (layer + 1) / heightSegments * 2 * shape.fDz, - r1 = Math.sqrt(r0 * r0 + tsq * z1 * z1), - r2 = Math.sqrt(r0 * r0 + tsq * z2 * z2); - - for (var seg = 0; seg < radiusSegments; ++seg) { - creator.AddFace4(r1 * _cos[seg + d1], r1 * _sin[seg + d1], z1, - r2 * _cos[seg + d1], r2 * _sin[seg + d1], z2, - r2 * _cos[seg + d2], r2 * _sin[seg + d2], z2, - r1 * _cos[seg + d2], r1 * _sin[seg + d2], z1); - creator.CalcNormal(); - } + creator.CalcNormal(); } } - // add caps - for (var layer = 0; layer < 2; ++layer) { - var z = (layer === 0) ? shape.fDz : -shape.fDz, - r1 = Math.sqrt(shape.fRmax * shape.fRmax + shape.fToutsq * z * z), - r2 = (shape.fRmin > 0) ? Math.sqrt(shape.fRmin * shape.fRmin + shape.fTinsq * z * z) : 0, - skip = (shape.fRmin > 0) ? 0 : 1, - d1 = 1 - layer, d2 = 1 - d1; + return creator.Create(); +} + +/** @memberOf GEO */ +export function createXtruBuffer(shape, faces_limit) { + let nfaces = (shape.fNz - 1) * shape.fNvert * 2; + + if (faces_limit < 0) return nfaces + shape.fNvert * 3; + + // create points + let pnts = []; + for (let vert = 0; vert < shape.fNvert; ++vert) + pnts.push(new THREE.Vector2(shape.fX[vert], shape.fY[vert])); + + // console.log('triangulate Xtru ' + shape.fShapeId); + let faces = THREE.ShapeUtils.triangulateShape(pnts, []); + if (faces.length < pnts.length - 2) { + warn('Problem with XTRU shape ' + shape.fName + ' with ' + pnts.length + ' vertices'); + faces = []; + } else { + nfaces += faces.length * 2; + } + + let creator = faces_limit ? new PolygonsCreator : new GeometryCreator(nfaces); + + for (let layer = 0; layer < shape.fNz - 1; ++layer) { + let z1 = shape.fZ[layer], scale1 = shape.fScale[layer], + z2 = shape.fZ[layer + 1], scale2 = shape.fScale[layer + 1], + x01 = shape.fX0[layer], x02 = shape.fX0[layer + 1], + y01 = shape.fY0[layer], y02 = shape.fY0[layer + 1]; + + for (let vert1 = 0; vert1 < shape.fNvert; ++vert1) { + let vert2 = (vert1 + 1) % shape.fNvert; + creator.AddFace4(scale1 * shape.fX[vert1] + x01, scale1 * shape.fY[vert1] + y01, z1, + scale2 * shape.fX[vert1] + x02, scale2 * shape.fY[vert1] + y02, z2, + scale2 * shape.fX[vert2] + x02, scale2 * shape.fY[vert2] + y02, z2, + scale1 * shape.fX[vert2] + x01, scale1 * shape.fY[vert2] + y01, z1); + creator.CalcNormal(); + } + } + + for (let layer = 0; layer <= shape.fNz - 1; layer += (shape.fNz - 1)) { + let z = shape.fZ[layer], scale = shape.fScale[layer], + x0 = shape.fX0[layer], y0 = shape.fY0[layer]; + + for (var n = 0; n < faces.length; ++n) { + let face = faces[n], + pnt1 = pnts[face[0]], + pnt2 = pnts[face[(layer === 0) ? 2 : 1]], + pnt3 = pnts[face[(layer === 0) ? 1 : 2]]; + + creator.AddFace3(scale * pnt1.x + x0, scale * pnt1.y + y0, z, + scale * pnt2.x + x0, scale * pnt2.y + y0, z, + scale * pnt3.x + x0, scale * pnt3.y + y0, z); + creator.SetNormal(0, 0, layer === 0 ? -1 : 1); + } + } + + return creator.Create(); +} + +/** @memberOf GEO */ +export function createParaboloidBuffer(shape, faces_limit) { + + var radiusSegments = Math.max(4, Math.round(360 / GradPerSegm)), + heightSegments = 30; + + if (faces_limit > 0) { + var fact = 2 * radiusSegments * (heightSegments + 1) / faces_limit; + if (fact > 1.) { + radiusSegments = Math.max(5, Math.floor(radiusSegments / Math.sqrt(fact))); + heightSegments = Math.max(5, Math.floor(heightSegments / Math.sqrt(fact))); + } + } + + var zmin = -shape.fDZ, zmax = shape.fDZ, rmin = shape.fRlo, rmax = shape.fRhi; + + // if no radius at -z, find intersection + if (shape.fA >= 0) { + if (shape.fB > zmin) zmin = shape.fB; + } else { + if (shape.fB < zmax) zmax = shape.fB; + } + + var ttmin = Math.atan2(zmin, rmin), ttmax = Math.atan2(zmax, rmax); + + var numfaces = (heightSegments + 1) * radiusSegments * 2; + if (rmin === 0) numfaces -= radiusSegments * 2; // complete layer + if (rmax === 0) numfaces -= radiusSegments * 2; // complete layer + + if (faces_limit < 0) return numfaces; + + // calculate all sin/cos tables in advance + var _sin = new Float32Array(radiusSegments + 1), + _cos = new Float32Array(radiusSegments + 1); + for (var seg = 0; seg <= radiusSegments; ++seg) { + _cos[seg] = Math.cos(seg / radiusSegments * 2 * Math.PI); + _sin[seg] = Math.sin(seg / radiusSegments * 2 * Math.PI); + } + + var creator = faces_limit ? new PolygonsCreator : new GeometryCreator(numfaces); + + var lastz = zmin, lastr = 0, lastnxy = 0, lastnz = -1; + + for (var layer = 0; layer <= heightSegments + 1; ++layer) { + + var layerz = 0, radius = 0, nxy = 0, nz = -1; + + if ((layer === 0) && (rmin === 0)) continue; + + if ((layer === heightSegments + 1) && (lastr === 0)) break; + + switch (layer) { + case 0: + layerz = zmin; + radius = rmin; + break; + case heightSegments: + layerz = zmax; + radius = rmax; + break; + case heightSegments + 1: + layerz = zmax; + radius = 0; + break; + default: { + var tt = Math.tan(ttmin + (ttmax - ttmin) * layer / heightSegments); + var delta = tt * tt - 4 * shape.fA * shape.fB; // should be always positive (a*b<0) + radius = 0.5 * (tt + Math.sqrt(delta)) / shape.fA; + if (radius < 1e-6) radius = 0; + layerz = radius * tt; + } + } + + nxy = shape.fA * radius; + nz = (shape.fA > 0) ? -1 : 1; + + var skip = 0; + if (lastr === 0) skip = 1; else if (radius === 0) skip = 2; + + for (var seg = 0; seg < radiusSegments; ++seg) { + creator.AddFace4(radius * _cos[seg], radius * _sin[seg], layerz, + lastr * _cos[seg], lastr * _sin[seg], lastz, + lastr * _cos[seg + 1], lastr * _sin[seg + 1], lastz, + radius * _cos[seg + 1], radius * _sin[seg + 1], layerz, skip); + + // use analytic normal values when open/closing paraboloid around 0 + // cut faces (top or bottom) set with simple normal + if ((skip === 0) || ((layer === 1) && (rmin === 0)) || ((layer === heightSegments + 1) && (rmax === 0))) + creator.SetNormal4(nxy * _cos[seg], nxy * _sin[seg], nz, + lastnxy * _cos[seg], lastnxy * _sin[seg], lastnz, + lastnxy * _cos[seg + 1], lastnxy * _sin[seg + 1], lastnz, + nxy * _cos[seg + 1], nxy * _sin[seg + 1], nz, skip); + else + creator.SetNormal(0, 0, (layer < heightSegments) ? -1 : 1); + } + + lastz = layerz; + lastr = radius; + lastnxy = nxy; + lastnz = nz; + } + + return creator.Create(); +} + +/** @memberOf GEO */ +export function createHypeBuffer(shape, faces_limit) { + + if ((shape.fTin === 0) && (shape.fTout === 0)) + return createTubeBuffer(shape, faces_limit); + + var radiusSegments = Math.max(4, Math.round(360 / GradPerSegm)), + heightSegments = 30; + + var numfaces = radiusSegments * (heightSegments + 1) * ((shape.fRmin > 0) ? 4 : 2); + + if (faces_limit < 0) return numfaces; + + if ((faces_limit > 0) && (faces_limit > numfaces)) { + radiusSegments = Math.max(4, Math.floor(radiusSegments / Math.sqrt(numfaces / faces_limit))); + heightSegments = Math.max(4, Math.floor(heightSegments / Math.sqrt(numfaces / faces_limit))); + numfaces = radiusSegments * (heightSegments + 1) * ((shape.fRmin > 0) ? 4 : 2); + } + + // calculate all sin/cos tables in advance + var _sin = new Float32Array(radiusSegments + 1), _cos = new Float32Array(radiusSegments + 1); + for (var seg = 0; seg <= radiusSegments; ++seg) { + _cos[seg] = Math.cos(seg / radiusSegments * 2 * Math.PI); + _sin[seg] = Math.sin(seg / radiusSegments * 2 * Math.PI); + } + + var creator = faces_limit ? new PolygonsCreator : new GeometryCreator(numfaces); + + // in-out side + for (var side = 0; side < 2; ++side) { + if ((side > 0) && (shape.fRmin <= 0)) break; + + var r0 = (side > 0) ? shape.fRmin : shape.fRmax, + tsq = (side > 0) ? shape.fTinsq : shape.fToutsq, + d1 = 1 - side, d2 = 1 - d1; + + // vertical layers + for (var layer = 0; layer < heightSegments; ++layer) { + var z1 = -shape.fDz + layer / heightSegments * 2 * shape.fDz, + z2 = -shape.fDz + (layer + 1) / heightSegments * 2 * shape.fDz, + r1 = Math.sqrt(r0 * r0 + tsq * z1 * z1), + r2 = Math.sqrt(r0 * r0 + tsq * z2 * z2); + for (var seg = 0; seg < radiusSegments; ++seg) { - creator.AddFace4(r1 * _cos[seg + d1], r1 * _sin[seg + d1], z, - r2 * _cos[seg + d1], r2 * _sin[seg + d1], z, - r2 * _cos[seg + d2], r2 * _sin[seg + d2], z, - r1 * _cos[seg + d2], r1 * _sin[seg + d2], z, skip); - creator.SetNormal(0, 0, (layer === 0) ? 1 : -1) + creator.AddFace4(r1 * _cos[seg + d1], r1 * _sin[seg + d1], z1, + r2 * _cos[seg + d1], r2 * _sin[seg + d1], z2, + r2 * _cos[seg + d2], r2 * _sin[seg + d2], z2, + r1 * _cos[seg + d2], r1 * _sin[seg + d2], z1); + creator.CalcNormal(); } + } + } + // add caps + for (var layer = 0; layer < 2; ++layer) { + var z = (layer === 0) ? shape.fDz : -shape.fDz, + r1 = Math.sqrt(shape.fRmax * shape.fRmax + shape.fToutsq * z * z), + r2 = (shape.fRmin > 0) ? Math.sqrt(shape.fRmin * shape.fRmin + shape.fTinsq * z * z) : 0, + skip = (shape.fRmin > 0) ? 0 : 1, + d1 = 1 - layer, d2 = 1 - d1; + for (var seg = 0; seg < radiusSegments; ++seg) { + creator.AddFace4(r1 * _cos[seg + d1], r1 * _sin[seg + d1], z, + r2 * _cos[seg + d1], r2 * _sin[seg + d1], z, + r2 * _cos[seg + d2], r2 * _sin[seg + d2], z, + r1 * _cos[seg + d2], r1 * _sin[seg + d2], z, skip); + creator.SetNormal(0, 0, (layer === 0) ? 1 : -1) } - return creator.Create(); - }; + } + return creator.Create(); +} - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.createMatrix = function (matrix) { +/** @memberOf GEO */ +function createMatrix(matrix) { - if (!matrix) return null; + if (!matrix) return null; - var translation = null, rotation = null, scale = null; + var translation = null, rotation = null, scale = null; - switch (matrix._typename) { - case 'TGeoTranslation': - translation = matrix.fTranslation; - break; - case 'TGeoRotation': - rotation = matrix.fRotationMatrix; - break; - case 'TGeoScale': - scale = matrix.fScale; - break; - case 'TGeoGenTrans': - scale = matrix.fScale; // no break, translation and rotation follows - case 'TGeoCombiTrans': - translation = matrix.fTranslation; - if (matrix.fRotation) rotation = matrix.fRotation.fRotationMatrix; - break; - case 'TGeoHMatrix': - translation = matrix.fTranslation; - rotation = matrix.fRotationMatrix; - scale = matrix.fScale; - break; - case 'TGeoIdentity': - break; - default: - console.warn('unsupported matrix ' + matrix._typename); - } + switch (matrix._typename) { + case 'TGeoTranslation': + translation = matrix.fTranslation; + break; + case 'TGeoRotation': + rotation = matrix.fRotationMatrix; + break; + case 'TGeoScale': + scale = matrix.fScale; + break; + case 'TGeoGenTrans': + scale = matrix.fScale; // no break, translation and rotation follows + case 'TGeoCombiTrans': + translation = matrix.fTranslation; + if (matrix.fRotation) rotation = matrix.fRotation.fRotationMatrix; + break; + case 'TGeoHMatrix': + translation = matrix.fTranslation; + rotation = matrix.fRotationMatrix; + scale = matrix.fScale; + break; + case 'TGeoIdentity': + break; + default: + console.warn('unsupported matrix ' + matrix._typename); + } - if (!translation && !rotation && !scale) return null; + if (!translation && !rotation && !scale) return null; - var res = new THREE.Matrix4(); + var res = new THREE.Matrix4(); - if (rotation) - res.set(rotation[0], rotation[1], rotation[2], 0, - rotation[3], rotation[4], rotation[5], 0, - rotation[6], rotation[7], rotation[8], 0, + if (rotation) + res.set(rotation[0], rotation[1], rotation[2], 0, + rotation[3], rotation[4], rotation[5], 0, + rotation[6], rotation[7], rotation[8], 0, + 0, 0, 0, 1); + + if (translation) + res.setPosition(new THREE.Vector3(translation[0], translation[1], translation[2])); + + if (scale) + res.scale(new THREE.Vector3(scale[0], scale[1], scale[2])); + + return res; +} + +/** @memberOf GEO */ +function getNodeMatrix(kind, node) { + // returns transformation matrix for the node + // created after node visibility flag is checked and volume cut is performed + + var matrix = null; + + if (kind === 1) { + // special handling for EVE nodes + + matrix = new THREE.Matrix4(); + + if (node.fTrans !== null) { + matrix.set(node.fTrans[0], node.fTrans[4], node.fTrans[8], 0, + node.fTrans[1], node.fTrans[5], node.fTrans[9], 0, + node.fTrans[2], node.fTrans[6], node.fTrans[10], 0, 0, 0, 0, 1); - - if (translation) - res.setPosition(new THREE.Vector3(translation[0], translation[1], translation[2])); - - if (scale) - res.scale(new THREE.Vector3(scale[0], scale[1], scale[2])); - - return res; - }; - - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.getNodeMatrix = function (kind, node) { - // returns transformation matrix for the node - // created after node visibility flag is checked and volume cut is performed - - var matrix = null; - - if (kind === 1) { - // special handling for EVE nodes - - matrix = new THREE.Matrix4(); - - if (node.fTrans !== null) { - matrix.set(node.fTrans[0], node.fTrans[4], node.fTrans[8], 0, - node.fTrans[1], node.fTrans[5], node.fTrans[9], 0, - node.fTrans[2], node.fTrans[6], node.fTrans[10], 0, - 0, 0, 0, 1); - // second - set position with proper sign - matrix.setPosition({x: node.fTrans[12], y: node.fTrans[13], z: node.fTrans[14]}); - } - } else if (('fMatrix' in node) && (node.fMatrix !== null)) - matrix = JSROOT.GEO.createMatrix(node.fMatrix); - else if ((node._typename == "TGeoNodeOffset") && (node.fFinder !== null)) { - let kPatternReflected = JSROOT.GEO.BITS.kVisBranch; - if ((node.fFinder.fBits & kPatternReflected) !== 0) - JSROOT.GEO.warn('Unsupported reflected pattern ' + node.fFinder._typename); - - // if (node.fFinder._typename === 'TGeoPatternCylR') { } - // if (node.fFinder._typename === 'TGeoPatternSphR') { } - // if (node.fFinder._typename === 'TGeoPatternSphTheta') { } - // if (node.fFinder._typename === 'TGeoPatternSphPhi') { } - // if (node.fFinder._typename === 'TGeoPatternHoneycomb') { } - switch (node.fFinder._typename) { - case 'TGeoPatternX': - case 'TGeoPatternY': - case 'TGeoPatternZ': - case 'TGeoPatternParaX': - case 'TGeoPatternParaY': - case 'TGeoPatternParaZ': - var _shift = node.fFinder.fStart + (node.fIndex + 0.5) * node.fFinder.fStep; - - matrix = new THREE.Matrix4(); - - switch (node.fFinder._typename[node.fFinder._typename.length - 1]) { - case 'X': - matrix.setPosition(new THREE.Vector3(_shift, 0, 0)); - break; - case 'Y': - matrix.setPosition(new THREE.Vector3(0, _shift, 0)); - break; - case 'Z': - matrix.setPosition(new THREE.Vector3(0, 0, _shift)); - break; - } - break; - - case 'TGeoPatternCylPhi': - var phi = (Math.PI / 180) * (node.fFinder.fStart + (node.fIndex + 0.5) * node.fFinder.fStep), - _cos = Math.cos(phi), _sin = Math.sin(phi); - - matrix = new THREE.Matrix4(); - - matrix.set(_cos, -_sin, 0, 0, - _sin, _cos, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1); - break; - - case 'TGeoPatternCylR': - // seems to be, require no transformation - matrix = new THREE.Matrix4(); - break; - - case 'TGeoPatternTrapZ': - var dz = node.fFinder.fStart + (node.fIndex + 0.5) * node.fFinder.fStep; - matrix = new THREE.Matrix4(); - matrix.setPosition(new THREE.Vector3(node.fFinder.fTxz * dz, node.fFinder.fTyz * dz, dz)); - break; - - default: - JSROOT.GEO.warn('Unsupported pattern type ' + node.fFinder._typename); - break; - } + // second - set position with proper sign + matrix.setPosition({x: node.fTrans[12], y: node.fTrans[13], z: node.fTrans[14]}); } + } else if (('fMatrix' in node) && (node.fMatrix !== null)) + matrix = createMatrix(node.fMatrix); + else if ((node._typename === "TGeoNodeOffset") && (node.fFinder !== null)) { + let kPatternReflected = BITS.kVisBranch; + if ((node.fFinder.fBits & kPatternReflected) !== 0) + warn('Unsupported reflected pattern ' + node.fFinder._typename); - return matrix; - }; + // if (node.fFinder._typename === 'TGeoPatternCylR') { } + // if (node.fFinder._typename === 'TGeoPatternSphR') { } + // if (node.fFinder._typename === 'TGeoPatternSphTheta') { } + // if (node.fFinder._typename === 'TGeoPatternSphPhi') { } + // if (node.fFinder._typename === 'TGeoPatternHoneycomb') { } + switch (node.fFinder._typename) { + case 'TGeoPatternX': + case 'TGeoPatternY': + case 'TGeoPatternZ': + case 'TGeoPatternParaX': + case 'TGeoPatternParaY': + case 'TGeoPatternParaZ': + var _shift = node.fFinder.fStart + (node.fIndex + 0.5) * node.fFinder.fStep; - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.createComposite = function (shape, faces_limit) { + matrix = new THREE.Matrix4(); - /* - if ((faces_limit === -1) || (faces_limit === 0)) { - var cnt = JSROOT.GEO.CountNumShapes(shape); - - if (cnt > JSROOT.GEO.CompLimit) { - JSROOT.GEO.warn("composite shape " + shape.fShapeId + " has " + cnt + " components, replace by most left"); - var matrix = new THREE.Matrix4(); - while (shape.fNode && shape.fNode.fLeft) { - var m1 = JSROOT.GEO.createMatrix(shape.fNode.fLeftMat); - if (m1) matrix.multiply(m1); - shape = shape.fNode.fLeft; - } - var res = JSROOT.GEO.createGeometry(shape, faces_limit); - if (res && (faces_limit===0)) res.applyMatrix(matrix); - return res; - } - } - */ - - if (faces_limit < 0) - return JSROOT.GEO.createGeometry(shape.fNode.fLeft, -10) + - JSROOT.GEO.createGeometry(shape.fNode.fRight, -10); - - var geom1, geom2, bsp1, bsp2, return_bsp = false, - matrix1 = JSROOT.GEO.createMatrix(shape.fNode.fLeftMat), - matrix2 = JSROOT.GEO.createMatrix(shape.fNode.fRightMat); - - // seems to be, IE has smaller stack for functions calls and ThreeCSG fails with large shapes - if (faces_limit === 0) faces_limit = (JSROOT.browser && JSROOT.browser.isIE) ? 2000 : 4000; - else return_bsp = true; - - if (matrix1 && (matrix1.determinant() < -0.9)) - JSROOT.GEO.warn('Axis reflection in left composite shape - not supported'); - - if (matrix2 && (matrix2.determinant() < -0.9)) - JSROOT.GEO.warn('Axis reflections in right composite shape - not supported'); - - geom1 = JSROOT.GEO.createGeometry(shape.fNode.fLeft, faces_limit); - if (!geom1) return null; - - var n1 = JSROOT.GEO.numGeometryFaces(geom1), n2 = 0; - if (geom1._exceed_limit) n1 += faces_limit; - - if (n1 < faces_limit) { - geom2 = JSROOT.GEO.createGeometry(shape.fNode.fRight, faces_limit); - n2 = JSROOT.GEO.numGeometryFaces(geom2); - } - - if ((n1 + n2 >= faces_limit) || !geom2) { - if (geom1.polygons) { - geom1 = ThreeBSP.CreateBufferGeometry(geom1.polygons); - n1 = JSROOT.GEO.numGeometryFaces(geom1); - } - if (matrix1) geom1.applyMatrix(matrix1); - // if (!geom1._exceed_limit) console.log('reach faces limit', faces_limit, 'got', n1, n2); - geom1._exceed_limit = true; - return geom1; - } - - bsp1 = new ThreeBSP.Geometry(geom1, matrix1, JSROOT.GEO.CompressComp ? 0 : undefined); - - bsp2 = new ThreeBSP.Geometry(geom2, matrix2, bsp1.maxid); - - // take over maxid from both geometries - bsp1.maxid = bsp2.maxid; - - switch (shape.fNode._typename) { - case 'TGeoIntersection': - bsp1.direct_intersect(bsp2); - break; // "*" - case 'TGeoUnion': - bsp1.direct_union(bsp2); - break; // "+" - case 'TGeoSubtraction': - bsp1.direct_subtract(bsp2); - break; // "/" - default: - JSROOT.GEO.warn('unsupported bool operation ' + shape.fNode._typename + ', use first geom'); - } - - if (JSROOT.GEO.numGeometryFaces(bsp1) === 0) { - JSROOT.GEO.warn('Zero faces in comp shape' - + ' left: ' + shape.fNode.fLeft._typename + ' ' + JSROOT.GEO.numGeometryFaces(geom1) + ' faces' - + ' right: ' + shape.fNode.fRight._typename + ' ' + JSROOT.GEO.numGeometryFaces(geom2) + ' faces' - + ' use first'); - bsp1 = new ThreeBSP.Geometry(geom1, matrix1); - } - - return return_bsp ? {polygons: bsp1.toPolygons()} : bsp1.toBufferGeometry(); - }; - - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.projectGeometry = function (geom, matrix, projection, position, flippedMesh) { - - if (!geom.boundingBox) geom.computeBoundingBox(); - - var box = geom.boundingBox.clone(); - - box.applyMatrix4(matrix); - - if (!position) position = 0; - - if (((box.min[projection] >= position) && (box.max[projection] >= position)) || - ((box.min[projection] <= position) && (box.max[projection] <= position))) { - return null; // not interesting - } - - var bsp1 = new ThreeBSP.Geometry(geom, matrix, 0, flippedMesh), - sizex = 2 * Math.max(Math.abs(box.min.x), Math.abs(box.max.x)), - sizey = 2 * Math.max(Math.abs(box.min.y), Math.abs(box.max.y)), - sizez = 2 * Math.max(Math.abs(box.min.z), Math.abs(box.max.z)), - size = 10000; - - switch (projection) { - case "x": - size = Math.max(sizey, sizez); - break; - case "y": - size = Math.max(sizex, sizez); - break; - case "z": - size = Math.max(sizex, sizey); - break; - } - - var bsp2 = ThreeBSP.CreateNormal(projection, position, size); - - bsp1.cut_from_plane(bsp2); - - return bsp2.toBufferGeometry(); - }; - - /** - * Creates geometry model for the provided shape - * @memberOf JSROOT.GEO - * - * If @par limit === 0 (or undefined) returns THREE.BufferGeometry - * If @par limit < 0 just returns estimated number of faces - * If @par limit > 0 return list of ThreeBSP polygons (used only for composite shapes) - * */ - JSROOT.GEO.createGeometry = function (shape, limit) { - if (limit === undefined) limit = 0; - - try { - switch (shape._typename) { - case "TGeoBBox": - return JSROOT.GEO.createCubeBuffer(shape, limit); - case "TGeoPara": - return JSROOT.GEO.createParaBuffer(shape, limit); - case "TGeoTrd1": - case "TGeoTrd2": - return JSROOT.GEO.createTrapezoidBuffer(shape, limit); - case "TGeoArb8": - case "TGeoTrap": - case "TGeoGtra": - return JSROOT.GEO.createArb8Buffer(shape, limit); - case "TGeoSphere": - return JSROOT.GEO.createSphereBuffer(shape, limit); - case "TGeoCone": - case "TGeoConeSeg": - case "TGeoTube": - case "TGeoTubeSeg": - case "TGeoCtub": - return JSROOT.GEO.createTubeBuffer(shape, limit); - case "TGeoEltu": - return JSROOT.GEO.createEltuBuffer(shape, limit); - case "TGeoTorus": - return JSROOT.GEO.createTorusBuffer(shape, limit); - case "TGeoPcon": - case "TGeoPgon": - return JSROOT.GEO.createPolygonBuffer(shape, limit); - case "TGeoXtru": - return JSROOT.GEO.createXtruBuffer(shape, limit); - case "TGeoParaboloid": - return JSROOT.GEO.createParaboloidBuffer(shape, limit); - case "TGeoHype": - return JSROOT.GEO.createHypeBuffer(shape, limit); - case "TGeoCompositeShape": - return JSROOT.GEO.createComposite(shape, limit); - case "TGeoShapeAssembly": - break; - case "TGeoScaledShape": { - var res = JSROOT.GEO.createGeometry(shape.fShape, limit); - if (shape.fScale && (limit >= 0) && (typeof res === 'object') && (typeof res.scale === 'function')) - res.scale(shape.fScale.fScale[0], shape.fScale.fScale[1], shape.fScale.fScale[2]); - return res; + switch (node.fFinder._typename[node.fFinder._typename.length - 1]) { + case 'X': + matrix.setPosition(new THREE.Vector3(_shift, 0, 0)); + break; + case 'Y': + matrix.setPosition(new THREE.Vector3(0, _shift, 0)); + break; + case 'Z': + matrix.setPosition(new THREE.Vector3(0, 0, _shift)); + break; } - default: - JSROOT.GEO.warn('unsupported shape type ' + shape._typename); - } - } catch (e) { - var place = ""; - if (e.stack !== undefined) { - place = e.stack.split("\n")[0]; - if (place.indexOf(e.message) >= 0) place = e.stack.split("\n")[1]; - else place = " at: " + place; - } - JSROOT.GEO.warn(shape._typename + " err: " + e.message + place); + break; + + case 'TGeoPatternCylPhi': + var phi = (Math.PI / 180) * (node.fFinder.fStart + (node.fIndex + 0.5) * node.fFinder.fStep), + _cos = Math.cos(phi), _sin = Math.sin(phi); + + matrix = new THREE.Matrix4(); + + matrix.set(_cos, -_sin, 0, 0, + _sin, _cos, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + break; + + case 'TGeoPatternCylR': + // seems to be, require no transformation + matrix = new THREE.Matrix4(); + break; + + case 'TGeoPatternTrapZ': + var dz = node.fFinder.fStart + (node.fIndex + 0.5) * node.fFinder.fStep; + matrix = new THREE.Matrix4(); + matrix.setPosition(new THREE.Vector3(node.fFinder.fTxz * dz, node.fFinder.fTyz * dz, dz)); + break; + + default: + warn('Unsupported pattern type ' + node.fFinder._typename); + break; } + } - return limit < 0 ? 0 : null; - }; + return matrix; +} - /** Provides info about geo object, used for tooltip info */ - JSROOT.GEO.provideInfo = function (obj) { - var info = [], shape = null; +/** @memberOf GEO */ +function createComposite(shape, faces_limit) { - if (obj.fVolume !== undefined) shape = obj.fVolume.fShape; else if (obj.fShape !== undefined) shape = obj.fShape; else if ((obj.fShapeBits !== undefined) && (obj.fShapeId !== undefined)) shape = obj; + /* + if ((faces_limit === -1) || (faces_limit === 0)) { + var cnt = CountNumShapes(shape); - if (!shape) { - info.push(obj._typename); - return info; + if (cnt > CompLimit) { + warn("composite shape " + shape.fShapeId + " has " + cnt + " components, replace by most left"); + var matrix = new THREE.Matrix4(); + while (shape.fNode && shape.fNode.fLeft) { + var m1 = createMatrix(shape.fNode.fLeftMat); + if (m1) matrix.multiply(m1); + shape = shape.fNode.fLeft; } + var res = createGeometry(shape, faces_limit); + if (res && (faces_limit===0)) res.applyMatrix(matrix); + return res; + } + } + */ - var sz = Math.max(shape.fDX, shape.fDY, shape.fDZ); - var useexp = (sz > 1e7) || (sz < 1e-7); + if (faces_limit < 0) + return createGeometry(shape.fNode.fLeft, -10) + + createGeometry(shape.fNode.fRight, -10); - function conv(v) { - if (v === undefined) return "???"; - if ((v == Math.round(v) && v < 1e7)) return Math.round(v); - return useexp ? v.toExponential(4) : v.toPrecision(7); + var geom1, geom2, bsp1, bsp2, return_bsp = false, + matrix1 = createMatrix(shape.fNode.fLeftMat), + matrix2 = createMatrix(shape.fNode.fRightMat); + + // seems to be, IE has smaller stack for functions calls and ThreeCSG fails with large shapes + //if (faces_limit === 0) faces_limit = (JSROOT.browser && JSROOT.browser.isIE) ? 2000 : 4000; + if(faces_limit ===0) faces_limit = 4000; + else return_bsp = true; + + if (matrix1 && (matrix1.determinant() < -0.9)) + warn('Axis reflection in left composite shape - not supported'); + + if (matrix2 && (matrix2.determinant() < -0.9)) + warn('Axis reflections in right composite shape - not supported'); + + geom1 = createGeometry(shape.fNode.fLeft, faces_limit); + if (!geom1) return null; + + var n1 = numGeometryFaces(geom1), n2 = 0; + if (geom1._exceed_limit) n1 += faces_limit; + + if (n1 < faces_limit) { + geom2 = createGeometry(shape.fNode.fRight, faces_limit); + n2 = numGeometryFaces(geom2); + } + + if ((n1 + n2 >= faces_limit) || !geom2) { + if (geom1.polygons) { + geom1 = ThreeBSP.CreateBufferGeometry(geom1.polygons); + n1 = numGeometryFaces(geom1); } + if (matrix1) geom1.applyMatrix(matrix1); + // if (!geom1._exceed_limit) console.log('reach faces limit', faces_limit, 'got', n1, n2); + geom1._exceed_limit = true; + return geom1; + } - info.push(shape._typename); + bsp1 = new ThreeBSP.Geometry(geom1, matrix1, CompressComp ? 0 : undefined); - info.push("DX=" + conv(shape.fDX) + " DY=" + conv(shape.fDY) + " DZ=" + conv(shape.fDZ)); + bsp2 = new ThreeBSP.Geometry(geom2, matrix2, bsp1.maxid); + // take over maxid from both geometries + bsp1.maxid = bsp2.maxid; + + switch (shape.fNode._typename) { + case 'TGeoIntersection': + bsp1.direct_intersect(bsp2); + break; // "*" + case 'TGeoUnion': + bsp1.direct_union(bsp2); + break; // "+" + case 'TGeoSubtraction': + bsp1.direct_subtract(bsp2); + break; // "/" + default: + warn('unsupported bool operation ' + shape.fNode._typename + ', use first geom'); + } + + if (numGeometryFaces(bsp1) === 0) { + warn('Zero faces in comp shape' + + ' left: ' + shape.fNode.fLeft._typename + ' ' + numGeometryFaces(geom1) + ' faces' + + ' right: ' + shape.fNode.fRight._typename + ' ' + numGeometryFaces(geom2) + ' faces' + + ' use first'); + bsp1 = new ThreeBSP.Geometry(geom1, matrix1); + } + + return return_bsp ? {polygons: bsp1.toPolygons()} : bsp1.toBufferGeometry(); +} + +/** @memberOf GEO */ +function projectGeometry(geom, matrix, projection, position, flippedMesh) { + + if (!geom.boundingBox) geom.computeBoundingBox(); + + var box = geom.boundingBox.clone(); + + box.applyMatrix4(matrix); + + if (!position) position = 0; + + if (((box.min[projection] >= position) && (box.max[projection] >= position)) || + ((box.min[projection] <= position) && (box.max[projection] <= position))) { + return null; // not interesting + } + + var bsp1 = new ThreeBSP.Geometry(geom, matrix, 0, flippedMesh), + sizex = 2 * Math.max(Math.abs(box.min.x), Math.abs(box.max.x)), + sizey = 2 * Math.max(Math.abs(box.min.y), Math.abs(box.max.y)), + sizez = 2 * Math.max(Math.abs(box.min.z), Math.abs(box.max.z)), + size = 10000; + + switch (projection) { + case "x": + size = Math.max(sizey, sizez); + break; + case "y": + size = Math.max(sizex, sizez); + break; + case "z": + size = Math.max(sizex, sizey); + break; + } + + var bsp2 = ThreeBSP.CreateNormal(projection, position, size); + + bsp1.cut_from_plane(bsp2); + + return bsp2.toBufferGeometry(); +} + + +/** + * Creates geometry model for the provided shape + * @memberOf GEO + * + * If @par limit === 0 (or undefined) returns THREE.BufferGeometry + * If @par limit < 0 just returns estimated number of faces + * If @par limit > 0 return list of ThreeBSP polygons (used only for composite shapes) + * */ +export function createGeometry(shape, limit) { + if (limit === undefined) limit = 0; + + try { switch (shape._typename) { case "TGeoBBox": - break; + return createCubeBuffer(shape, limit); case "TGeoPara": - info.push("Alpha=" + shape.fAlpha + " Phi=" + shape.fPhi + " Theta=" + shape.fTheta); - break; - case "TGeoTrd2": - info.push("Dy1=" + conv(shape.fDy1) + " Dy2=" + conv(shape.fDy1)); + return createParaBuffer(shape, limit); case "TGeoTrd1": - info.push("Dx1=" + conv(shape.fDx1) + " Dx2=" + conv(shape.fDx1)); - break; + case "TGeoTrd2": + return createTrapezoidBuffer(shape, limit); case "TGeoArb8": - break; case "TGeoTrap": - break; case "TGeoGtra": - break; + return createArb8Buffer(shape, limit); case "TGeoSphere": - info.push("Rmin=" + conv(shape.fRmin) + " Rmax=" + conv(shape.fRmax)); - info.push("Phi1=" + shape.fPhi1 + " Phi2=" + shape.fPhi2); - info.push("Theta1=" + shape.fTheta1 + " Theta2=" + shape.fTheta2); - break; - case "TGeoConeSeg": - info.push("Phi1=" + shape.fPhi1 + " Phi2=" + shape.fPhi2); + return createSphereBuffer(shape, limit); case "TGeoCone": - info.push("Rmin1=" + conv(shape.fRmin1) + " Rmax1=" + conv(shape.fRmax1)); - info.push("Rmin2=" + conv(shape.fRmin2) + " Rmax2=" + conv(shape.fRmax2)); - break; - case "TGeoCtub": - case "TGeoTubeSeg": - info.push("Phi1=" + shape.fPhi1 + " Phi2=" + shape.fPhi2); - case "TGeoEltu": + case "TGeoConeSeg": case "TGeoTube": - info.push("Rmin=" + conv(shape.fRmin) + " Rmax=" + conv(shape.fRmax)); - break; + case "TGeoTubeSeg": + case "TGeoCtub": + return createTubeBuffer(shape, limit); + case "TGeoEltu": + return createEltuBuffer(shape, limit); case "TGeoTorus": - info.push("Rmin=" + conv(shape.fRmin) + " Rmax=" + conv(shape.fRmax)); - info.push("Phi1=" + shape.fPhi1 + " Dphi=" + shape.fDphi); - break; + return createTorusBuffer(shape, limit); case "TGeoPcon": case "TGeoPgon": - break; + return createPolygonBuffer(shape, limit); case "TGeoXtru": - break; + return createXtruBuffer(shape, limit); case "TGeoParaboloid": - info.push("Rlo=" + conv(shape.fRlo) + " Rhi=" + conv(shape.fRhi)); - info.push("A=" + conv(shape.fA) + " B=" + conv(shape.fB)); - break; + return createParaboloidBuffer(shape, limit); case "TGeoHype": - info.push("Rmin=" + conv(shape.fRmin) + " Rmax=" + conv(shape.fRmax)); - info.push("StIn=" + conv(shape.fStIn) + " StOut=" + conv(shape.fStOut)); - break; + return createHypeBuffer(shape, limit); case "TGeoCompositeShape": - break; + return createComposite(shape, limit); case "TGeoShapeAssembly": break; - case "TGeoScaledShape": - info = JSROOT.GEO.provideInfo(shape.fShape); - if (shape.fScale) - info.unshift('Scale X=' + shape.fScale.fScale[0] + " Y=" + shape.fScale.fScale[1] + " Z=" + shape.fScale.fScale[2]); - break; - } - - return info; - }; - - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.CreateProjectionMatrix = function (camera) { - var cameraProjectionMatrix = new THREE.Matrix4(); - - camera.updateMatrixWorld(); - camera.matrixWorldInverse.getInverse(camera.matrixWorld); - cameraProjectionMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); - - return cameraProjectionMatrix; - }; - - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.CreateFrustum = function (source) { - if (!source) return null; - - if (source instanceof THREE.PerspectiveCamera) - source = JSROOT.GEO.CreateProjectionMatrix(source); - - var frustum = new THREE.Frustum(); - frustum.setFromMatrix(source); - - frustum.corners = new Float32Array([ - 1, 1, 1, - 1, 1, -1, - 1, -1, 1, - 1, -1, -1, - -1, 1, 1, - -1, 1, -1, - -1, -1, 1, - -1, -1, -1, - 0, 0, 0 // also check center of the shape - ]); - - frustum.test = new THREE.Vector3(0, 0, 0); - - frustum.CheckShape = function (matrix, shape) { - var pnt = this.test, len = this.corners.length, corners = this.corners, i; - - for (i = 0; i < len; i += 3) { - pnt.x = corners[i] * shape.fDX; - pnt.y = corners[i + 1] * shape.fDY; - pnt.z = corners[i + 2] * shape.fDZ; - if (this.containsPoint(pnt.applyMatrix4(matrix))) return true; + case "TGeoScaledShape": { + var res = createGeometry(shape.fShape, limit); + if (shape.fScale && (limit >= 0) && (typeof res === 'object') && (typeof res.scale === 'function')) + res.scale(shape.fScale.fScale[0], shape.fScale.fScale[1], shape.fScale.fScale[2]); + return res; } + default: + warn('unsupported shape type ' + shape._typename); + } + } catch (e) { + var place = ""; + if (e.stack !== undefined) { + place = e.stack.split("\n")[0]; + if (place.indexOf(e.message) >= 0) place = e.stack.split("\n")[1]; + else place = " at: " + place; + } + warn(shape._typename + " err: " + e.message + place); + } - return false; - }; + return limit < 0 ? 0 : null; +} - frustum.CheckBox = function (box) { - var pnt = this.test, cnt = 0; - pnt.set(box.min.x, box.min.y, box.min.z); - if (this.containsPoint(pnt)) cnt++; - pnt.set(box.min.x, box.min.y, box.max.z); - if (this.containsPoint(pnt)) cnt++; - pnt.set(box.min.x, box.max.y, box.min.z); - if (this.containsPoint(pnt)) cnt++; - pnt.set(box.min.x, box.max.y, box.max.z); - if (this.containsPoint(pnt)) cnt++; - pnt.set(box.max.x, box.max.y, box.max.z); - if (this.containsPoint(pnt)) cnt++; - pnt.set(box.max.x, box.min.y, box.max.z); - if (this.containsPoint(pnt)) cnt++; - pnt.set(box.max.x, box.max.y, box.min.z); - if (this.containsPoint(pnt)) cnt++; - pnt.set(box.max.x, box.max.y, box.max.z); - if (this.containsPoint(pnt)) cnt++; - return cnt > 5; // only if 6 edges and more are seen, we think that box is fully visible - }; +/** Provides info about geo object, used for tooltip info */ +function provideInfo(obj) { + var info = [], shape = null; - return frustum; - }; + if (obj.fVolume !== undefined) shape = obj.fVolume.fShape; else if (obj.fShape !== undefined) shape = obj.fShape; else if ((obj.fShapeBits !== undefined) && (obj.fShapeId !== undefined)) shape = obj; - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.VisibleByCamera = function (camera, matrix, shape) { - var frustum = new THREE.Frustum(); - var cameraProjectionMatrix = new THREE.Matrix4(); + if (!shape) { + info.push(obj._typename); + return info; + } - camera.updateMatrixWorld(); - camera.matrixWorldInverse.getInverse(camera.matrixWorld); - cameraProjectionMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); - frustum.setFromMatrix(cameraProjectionMatrix); + var sz = Math.max(shape.fDX, shape.fDY, shape.fDZ); + var useexp = (sz > 1e7) || (sz < 1e-7); - var corners = [ - new THREE.Vector3(shape.fDX / 2.0, shape.fDY / 2.0, shape.fDZ / 2.0), - new THREE.Vector3(shape.fDX / 2.0, shape.fDY / 2.0, -shape.fDZ / 2.0), - new THREE.Vector3(shape.fDX / 2.0, -shape.fDY / 2.0, shape.fDZ / 2.0), - new THREE.Vector3(shape.fDX / 2.0, -shape.fDY / 2.0, -shape.fDZ / 2.0), - new THREE.Vector3(-shape.fDX / 2.0, shape.fDY / 2.0, shape.fDZ / 2.0), - new THREE.Vector3(-shape.fDX / 2.0, shape.fDY / 2.0, -shape.fDZ / 2.0), - new THREE.Vector3(-shape.fDX / 2.0, -shape.fDY / 2.0, shape.fDZ / 2.0), - new THREE.Vector3(-shape.fDX / 2.0, -shape.fDY / 2.0, -shape.fDZ / 2.0) - ]; - for (var i = 0; i < corners.length; i++) { - if (frustum.containsPoint(corners[i].applyMatrix4(matrix))) return true; + function conv(v) { + if (v === undefined) return "???"; + if ((v === Math.round(v) && v < 1e7)) return Math.round(v); + return useexp ? v.toExponential(4) : v.toPrecision(7); + } + + info.push(shape._typename); + + info.push("DX=" + conv(shape.fDX) + " DY=" + conv(shape.fDY) + " DZ=" + conv(shape.fDZ)); + + switch (shape._typename) { + case "TGeoBBox": + break; + case "TGeoPara": + info.push("Alpha=" + shape.fAlpha + " Phi=" + shape.fPhi + " Theta=" + shape.fTheta); + break; + case "TGeoTrd2": + info.push("Dy1=" + conv(shape.fDy1) + " Dy2=" + conv(shape.fDy1)); + case "TGeoTrd1": + info.push("Dx1=" + conv(shape.fDx1) + " Dx2=" + conv(shape.fDx1)); + break; + case "TGeoArb8": + break; + case "TGeoTrap": + break; + case "TGeoGtra": + break; + case "TGeoSphere": + info.push("Rmin=" + conv(shape.fRmin) + " Rmax=" + conv(shape.fRmax)); + info.push("Phi1=" + shape.fPhi1 + " Phi2=" + shape.fPhi2); + info.push("Theta1=" + shape.fTheta1 + " Theta2=" + shape.fTheta2); + break; + case "TGeoConeSeg": + info.push("Phi1=" + shape.fPhi1 + " Phi2=" + shape.fPhi2); + case "TGeoCone": + info.push("Rmin1=" + conv(shape.fRmin1) + " Rmax1=" + conv(shape.fRmax1)); + info.push("Rmin2=" + conv(shape.fRmin2) + " Rmax2=" + conv(shape.fRmax2)); + break; + case "TGeoCtub": + case "TGeoTubeSeg": + info.push("Phi1=" + shape.fPhi1 + " Phi2=" + shape.fPhi2); + case "TGeoEltu": + case "TGeoTube": + info.push("Rmin=" + conv(shape.fRmin) + " Rmax=" + conv(shape.fRmax)); + break; + case "TGeoTorus": + info.push("Rmin=" + conv(shape.fRmin) + " Rmax=" + conv(shape.fRmax)); + info.push("Phi1=" + shape.fPhi1 + " Dphi=" + shape.fDphi); + break; + case "TGeoPcon": + case "TGeoPgon": + break; + case "TGeoXtru": + break; + case "TGeoParaboloid": + info.push("Rlo=" + conv(shape.fRlo) + " Rhi=" + conv(shape.fRhi)); + info.push("A=" + conv(shape.fA) + " B=" + conv(shape.fB)); + break; + case "TGeoHype": + info.push("Rmin=" + conv(shape.fRmin) + " Rmax=" + conv(shape.fRmax)); + info.push("StIn=" + conv(shape.fStIn) + " StOut=" + conv(shape.fStOut)); + break; + case "TGeoCompositeShape": + break; + case "TGeoShapeAssembly": + break; + case "TGeoScaledShape": + info = provideInfo(shape.fShape); + if (shape.fScale) + info.unshift('Scale X=' + shape.fScale.fScale[0] + " Y=" + shape.fScale.fScale[1] + " Z=" + shape.fScale.fScale[2]); + break; + } + + return info; +} + +/** @memberOf GEO */ +function CreateProjectionMatrix(camera) { + var cameraProjectionMatrix = new THREE.Matrix4(); + + camera.updateMatrixWorld(); + camera.matrixWorldInverse.getInverse(camera.matrixWorld); + cameraProjectionMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); + + return cameraProjectionMatrix; +} + +/** @memberOf GEO */ +function CreateFrustum(source) { + if (!source) return null; + + if (source instanceof THREE.PerspectiveCamera) + source = CreateProjectionMatrix(source); + + var frustum = new THREE.Frustum(); + frustum.setFromMatrix(source); + + frustum.corners = new Float32Array([ + 1, 1, 1, + 1, 1, -1, + 1, -1, 1, + 1, -1, -1, + -1, 1, 1, + -1, 1, -1, + -1, -1, 1, + -1, -1, -1, + 0, 0, 0 // also check center of the shape + ]); + + frustum.test = new THREE.Vector3(0, 0, 0); + + frustum.CheckShape = function (matrix, shape) { + var pnt = this.test, len = this.corners.length, corners = this.corners, i; + + for (i = 0; i < len; i += 3) { + pnt.x = corners[i] * shape.fDX; + pnt.y = corners[i + 1] * shape.fDY; + pnt.z = corners[i + 2] * shape.fDZ; + if (this.containsPoint(pnt.applyMatrix4(matrix))) return true; } return false; }; - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.numGeometryFaces = function (geom) { - if (!geom) return 0; - - if (geom instanceof ThreeBSP.Geometry) - return geom.tree.numPolygons(); - - if (geom.type == 'BufferGeometry') { - var attr = geom.getAttribute('position'); - return attr ? attr.count / 3 : 0; - } - - // special array of polygons - if (geom && geom.polygons) return geom.polygons.length; - - return geom.faces.length; + frustum.CheckBox = function (box) { + var pnt = this.test, cnt = 0; + pnt.set(box.min.x, box.min.y, box.min.z); + if (this.containsPoint(pnt)) cnt++; + pnt.set(box.min.x, box.min.y, box.max.z); + if (this.containsPoint(pnt)) cnt++; + pnt.set(box.min.x, box.max.y, box.min.z); + if (this.containsPoint(pnt)) cnt++; + pnt.set(box.min.x, box.max.y, box.max.z); + if (this.containsPoint(pnt)) cnt++; + pnt.set(box.max.x, box.max.y, box.max.z); + if (this.containsPoint(pnt)) cnt++; + pnt.set(box.max.x, box.min.y, box.max.z); + if (this.containsPoint(pnt)) cnt++; + pnt.set(box.max.x, box.max.y, box.min.z); + if (this.containsPoint(pnt)) cnt++; + pnt.set(box.max.x, box.max.y, box.max.z); + if (this.containsPoint(pnt)) cnt++; + return cnt > 5; // only if 6 edges and more are seen, we think that box is fully visible }; - /** @memberOf JSROOT.GEO */ - JSROOT.GEO.numGeometryVertices = function (geom) { - if (!geom) return 0; + return frustum; +} - if (geom instanceof ThreeBSP.Geometry) - return geom.tree.numPolygons() * 3; +/** @memberOf GEO */ +function VisibleByCamera(camera, matrix, shape) { + var frustum = new THREE.Frustum(); + var cameraProjectionMatrix = new THREE.Matrix4(); - if (geom.type == 'BufferGeometry') { - var attr = geom.getAttribute('position'); - return attr ? attr.count : 0; - } + camera.updateMatrixWorld(); + camera.matrixWorldInverse.getInverse(camera.matrixWorld); + cameraProjectionMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); + frustum.setFromMatrix(cameraProjectionMatrix); - if (geom && geom.polygons) return geom.polygons.length * 4; + var corners = [ + new THREE.Vector3(shape.fDX / 2.0, shape.fDY / 2.0, shape.fDZ / 2.0), + new THREE.Vector3(shape.fDX / 2.0, shape.fDY / 2.0, -shape.fDZ / 2.0), + new THREE.Vector3(shape.fDX / 2.0, -shape.fDY / 2.0, shape.fDZ / 2.0), + new THREE.Vector3(shape.fDX / 2.0, -shape.fDY / 2.0, -shape.fDZ / 2.0), + new THREE.Vector3(-shape.fDX / 2.0, shape.fDY / 2.0, shape.fDZ / 2.0), + new THREE.Vector3(-shape.fDX / 2.0, shape.fDY / 2.0, -shape.fDZ / 2.0), + new THREE.Vector3(-shape.fDX / 2.0, -shape.fDY / 2.0, shape.fDZ / 2.0), + new THREE.Vector3(-shape.fDX / 2.0, -shape.fDY / 2.0, -shape.fDZ / 2.0) + ]; + for (var i = 0; i < corners.length; i++) { + if (frustum.containsPoint(corners[i].applyMatrix4(matrix))) return true; + } - return geom.vertices.length; - }; + return false; +} - /** Compares two stacks. Returns length where stacks are the same - * @memberOf JSROOT.GEO - * @private */ - JSROOT.GEO.CompareStacks = function (stack1, stack2) { - if (!stack1 || !stack2) return 0; - if (stack1 === stack2) return stack1.length; - var len = Math.min(stack1.length, stack2.length); - for (var k = 0; k < len; ++k) - if (stack1[k] !== stack2[k]) return k; - return len; - }; +/** @memberOf GEO */ +function numGeometryFaces(geom) { + if (!geom) return 0; - /** Checks if two stack arrays are identical - * @memberOf JSROOT.GEO - * @private */ - JSROOT.GEO.IsSameStack = function (stack1, stack2) { - if (!stack1 || !stack2) return false; - if (stack1 === stack2) return true; - if (stack1.length !== stack2.length) return false; - for (var k = 0; k < stack1.length; ++k) - if (stack1[k] !== stack2[k]) return false; - return true; - }; + if (geom instanceof ThreeBSP.Geometry) + return geom.tree.numPolygons(); + + if (geom.type === 'BufferGeometry') { + var attr = geom.getAttribute('position'); + return attr ? attr.count / 3 : 0; + } + + // special array of polygons + if (geom && geom.polygons) return geom.polygons.length; + + return geom.faces.length; +} + +/** @memberOf GEO */ +function numGeometryVertices(geom) { + if (!geom) return 0; + + if (geom instanceof ThreeBSP.Geometry) + return geom.tree.numPolygons() * 3; + + if (geom.type === 'BufferGeometry') { + var attr = geom.getAttribute('position'); + return attr ? attr.count : 0; + } + + if (geom && geom.polygons) return geom.polygons.length * 4; + + return geom.vertices.length; +} + +/** Compares two stacks. Returns length where stacks are the same + * @memberOf GEO + * @private */ +function CompareStacks(stack1, stack2) { + if (!stack1 || !stack2) return 0; + if (stack1 === stack2) return stack1.length; + var len = Math.min(stack1.length, stack2.length); + for (var k = 0; k < len; ++k) + if (stack1[k] !== stack2[k]) return k; + return len; +} + +/** Checks if two stack arrays are identical + * @memberOf GEO + * @private */ +function IsSameStack(stack1, stack2) { + if (!stack1 || !stack2) return false; + if (stack1 === stack2) return true; + if (stack1.length !== stack2.length) return false; + for (var k = 0; k < stack1.length; ++k) + if (stack1[k] !== stack2[k]) return false; + return true; +} - // ==================================================================== +// ==================================================================== - // class for working with cloned nodes +// class for working with cloned nodes - JSROOT.GEO.ClonedNodes = function (obj, clones) { +class ClonedNodes { + constructor(obj, clones) { this.toplevel = true; // indicate if object creates top-level structure with Nodes and Volumes folder this.name_prefix = ""; // name prefix used for nodes names this.maxdepth = 1; // maximal hierarchy depth, required for transparency @@ -2150,1377 +2137,1425 @@ this.CreateClones(obj); } else if (clones) this.nodes = clones; }; +} - JSROOT.GEO.ClonedNodes.prototype.GetNodeShape = function (indx) { - if (!this.origin || !this.nodes) return null; - var obj = this.origin[indx], clone = this.nodes[indx]; - if (!obj || !clone) return null; - if (clone.kind === 0) { - if (obj.fVolume) return obj.fVolume.fShape; - } else { - return obj.fShape; + +ClonedNodes = function (obj, clones) { + this.toplevel = true; // indicate if object creates top-level structure with Nodes and Volumes folder + this.name_prefix = ""; // name prefix used for nodes names + this.maxdepth = 1; // maximal hierarchy depth, required for transparency + + if (obj) { + if (obj.$geoh) this.toplevel = false; + this.CreateClones(obj); + } else if (clones) this.nodes = clones; +}; + +ClonedNodes.prototype.GetNodeShape = function (indx) { + if (!this.origin || !this.nodes) return null; + var obj = this.origin[indx], clone = this.nodes[indx]; + if (!obj || !clone) return null; + if (clone.kind === 0) { + if (obj.fVolume) return obj.fVolume.fShape; + } else { + return obj.fShape; + } + return null; +}; + +ClonedNodes.prototype.Cleanup = function (drawnodes, drawshapes) { + // function to cleanup as much as possible structures + // drawnodes and drawshapes are arrays created during building of geometry + + if (drawnodes) { + for (var n = 0; n < drawnodes.length; ++n) { + delete drawnodes[n].stack; + drawnodes[n] = undefined; } - return null; - }; + } - JSROOT.GEO.ClonedNodes.prototype.Cleanup = function (drawnodes, drawshapes) { - // function to cleanup as much as possible structures - // drawnodes and drawshapes are arrays created during building of geometry + if (drawshapes) { + for (var n = 0; n < drawshapes.length; ++n) { + delete drawshapes[n].geom; + drawshapes[n] = undefined; + } + } - if (drawnodes) { - for (var n = 0; n < drawnodes.length; ++n) { - delete drawnodes[n].stack; - drawnodes[n] = undefined; + if (this.nodes) + for (var n = 0; n < this.nodes.length; ++n) + delete this.nodes[n].chlds; + + delete this.nodes; + delete this.origin; + + delete this.sortmap; + +}; + +ClonedNodes.prototype.CreateClones = function (obj, sublevel, kind) { + if (!sublevel) { + this.origin = []; + sublevel = 1; + kind = NodeKind(obj); + } + + if ((kind < 0) || !obj || ('_refid' in obj)) return; + + obj._refid = this.origin.length; + this.origin.push(obj); + if (sublevel > this.maxdepth) this.maxdepth = sublevel; + + var chlds = null; + if (kind === 0) + chlds = (obj.fVolume && obj.fVolume.fNodes) ? obj.fVolume.fNodes.arr : null; + else + chlds = obj.fElements ? obj.fElements.arr : null; + + if (chlds !== null) { + CheckDuplicates(obj, chlds); + for (var i = 0; i < chlds.length; ++i) + this.CreateClones(chlds[i], sublevel + 1, kind); + } + + if (sublevel > 1) return; + + this.nodes = []; + + var sortarr = []; + + // first create nodes objects + for (var n = 0; n < this.origin.length; ++n) { + var obj = this.origin[n]; + var node = {id: n, kind: kind, vol: 0, nfaces: 0, numvischld: 1, idshift: 0}; + this.nodes.push(node); + sortarr.push(node); // array use to produce sortmap + } + + // than fill children lists + for (var n = 0; n < this.origin.length; ++n) { + var obj = this.origin[n], clone = this.nodes[n]; + + var chlds = null, shape = null; + + if (kind === 1) { + shape = obj.fShape; + if (obj.fElements) chlds = obj.fElements.arr; + } else if (obj.fVolume) { + shape = obj.fVolume.fShape; + if (obj.fVolume.fNodes) chlds = obj.fVolume.fNodes.arr; + } + + const matrix = getNodeMatrix(kind, obj); + if (matrix) { + clone.matrix = matrix.elements; // take only matrix elements, matrix will be constructed in worker + if (clone.matrix[0] === 1) { + var issimple = true; + for (let k = 1; (k < clone.matrix.length) && issimple; ++k) + issimple = (clone.matrix[k] === ((k === 5) || (k === 10) || (k === 15) ? 1 : 0)); + if (issimple) delete clone.matrix; } } + if (shape) { + clone.fDX = shape.fDX; + clone.fDY = shape.fDY; + clone.fDZ = shape.fDZ; + clone.vol = shape.fDX * shape.fDY * shape.fDZ; + if (shape.$nfaces === undefined) + shape.$nfaces = createGeometry(shape, -1); + clone.nfaces = shape.$nfaces; + if (clone.nfaces <= 0) clone.vol = 0; - if (drawshapes) { - for (var n = 0; n < drawshapes.length; ++n) { - delete drawshapes[n].geom; - drawshapes[n] = undefined; - } + // if (clone.nfaces < -10) console.log('Problem with node ' + obj.fName + ':' + obj.fMother.fName); } - if (this.nodes) - for (var n = 0; n < this.nodes.length; ++n) - delete this.nodes[n].chlds; + if (!chlds) continue; - delete this.nodes; - delete this.origin; + // in cloned object children is only list of ids + clone.chlds = new Int32Array(chlds.length); + for (var k = 0; k < chlds.length; ++k) + clone.chlds[k] = chlds[k]._refid; + } - delete this.sortmap; + // remove _refid identifiers from original objects + for (var n = 0; n < this.origin.length; ++n) + delete this.origin[n]._refid; - }; + // do sorting once + sortarr.sort(function (a, b) { + return b.vol - a.vol; + }); - JSROOT.GEO.ClonedNodes.prototype.CreateClones = function (obj, sublevel, kind) { - if (!sublevel) { - this.origin = []; - sublevel = 1; - kind = JSROOT.GEO.NodeKind(obj); - } - - if ((kind < 0) || !obj || ('_refid' in obj)) return; - - obj._refid = this.origin.length; - this.origin.push(obj); - if (sublevel > this.maxdepth) this.maxdepth = sublevel; - - var chlds = null; - if (kind === 0) - chlds = (obj.fVolume && obj.fVolume.fNodes) ? obj.fVolume.fNodes.arr : null; - else - chlds = obj.fElements ? obj.fElements.arr : null; - - if (chlds !== null) { - JSROOT.GEO.CheckDuplicates(obj, chlds); - for (var i = 0; i < chlds.length; ++i) - this.CreateClones(chlds[i], sublevel + 1, kind); - } - - if (sublevel > 1) return; - - this.nodes = []; - - var sortarr = []; - - // first create nodes objects - for (var n = 0; n < this.origin.length; ++n) { - var obj = this.origin[n]; - var node = {id: n, kind: kind, vol: 0, nfaces: 0, numvischld: 1, idshift: 0}; - this.nodes.push(node); - sortarr.push(node); // array use to produce sortmap - } - - // than fill children lists - for (var n = 0; n < this.origin.length; ++n) { - var obj = this.origin[n], clone = this.nodes[n]; - - var chlds = null, shape = null; - - if (kind === 1) { - shape = obj.fShape; - if (obj.fElements) chlds = obj.fElements.arr; - } else if (obj.fVolume) { - shape = obj.fVolume.fShape; - if (obj.fVolume.fNodes) chlds = obj.fVolume.fNodes.arr; - } - - const matrix = JSROOT.GEO.getNodeMatrix(kind, obj); - if (matrix) { - clone.matrix = matrix.elements; // take only matrix elements, matrix will be constructed in worker - if (clone.matrix[0] === 1) { - var issimple = true; - for (let k = 1; (k < clone.matrix.length) && issimple; ++k) - issimple = (clone.matrix[k] === ((k === 5) || (k === 10) || (k === 15) ? 1 : 0)); - if (issimple) delete clone.matrix; - } - } - if (shape) { - clone.fDX = shape.fDX; - clone.fDY = shape.fDY; - clone.fDZ = shape.fDZ; - clone.vol = shape.fDX * shape.fDY * shape.fDZ; - if (shape.$nfaces === undefined) - shape.$nfaces = JSROOT.GEO.createGeometry(shape, -1); - clone.nfaces = shape.$nfaces; - if (clone.nfaces <= 0) clone.vol = 0; - - // if (clone.nfaces < -10) console.log('Problem with node ' + obj.fName + ':' + obj.fMother.fName); - } - - if (!chlds) continue; - - // in cloned object children is only list of ids - clone.chlds = new Int32Array(chlds.length); - for (var k = 0; k < chlds.length; ++k) - clone.chlds[k] = chlds[k]._refid; - } - - // remove _refid identifiers from original objects - for (var n = 0; n < this.origin.length; ++n) - delete this.origin[n]._refid; - - // do sorting once - sortarr.sort(function (a, b) { - return b.vol - a.vol; - }); - - // remember sort map and also sortid - this.sortmap = new Int32Array(this.nodes.length); - for (var n = 0; n < this.nodes.length; ++n) { - this.sortmap[n] = sortarr[n].id; - sortarr[n].sortid = n; - } - }; + // remember sort map and also sortid + this.sortmap = new Int32Array(this.nodes.length); + for (var n = 0; n < this.nodes.length; ++n) { + this.sortmap[n] = sortarr[n].id; + sortarr[n].sortid = n; + } +}; - JSROOT.GEO.ClonedNodes.prototype.MarkVisisble = function (on_screen, copy_bits, cloning) { - if (!this.nodes) return 0; +ClonedNodes.prototype.MarkVisisble = function (on_screen, copy_bits, cloning) { + if (!this.nodes) return 0; - var res = 0, simple_copy = cloning && (cloning.length === this.nodes.length); + var res = 0, simple_copy = cloning && (cloning.length === this.nodes.length); - if (!simple_copy && !this.origin) return 0; + if (!simple_copy && !this.origin) return 0; - for (var n = 0; n < this.nodes.length; ++n) { - var clone = this.nodes[n]; + for (var n = 0; n < this.nodes.length; ++n) { + var clone = this.nodes[n]; - clone.vis = false; - clone.numvischld = 1; // reset vis counter, will be filled with next scan - clone.idshift = 0; - delete clone.depth; - - if (simple_copy) { - clone.vis = cloning[n].vis; - if (cloning[n].depth !== undefined) clone.depth = cloning[n].depth; - if (clone.vis) res++; - continue; - } - - var obj = this.origin[n]; - - if (clone.kind === 0) { - if (obj.fVolume) { - if (on_screen) { - clone.vis = JSROOT.GEO.TestBit(obj.fVolume, JSROOT.GEO.BITS.kVisOnScreen); - if (copy_bits) { - JSROOT.GEO.SetBit(obj.fVolume, JSROOT.GEO.BITS.kVisNone, false); - JSROOT.GEO.SetBit(obj.fVolume, JSROOT.GEO.BITS.kVisThis, clone.vis); - JSROOT.GEO.SetBit(obj.fVolume, JSROOT.GEO.BITS.kVisDaughters, true); - } - } else { - clone.vis = !JSROOT.GEO.TestBit(obj.fVolume, JSROOT.GEO.BITS.kVisNone) && - JSROOT.GEO.TestBit(obj.fVolume, JSROOT.GEO.BITS.kVisThis) && !obj.fFinder; - if (!JSROOT.GEO.TestBit(obj.fVolume, JSROOT.GEO.BITS.kVisDaughters)) - clone.depth = JSROOT.GEO.TestBit(obj.fVolume, JSROOT.GEO.BITS.kVisOneLevel) ? 1 : 0; - } - } - } else { - clone.vis = obj.fRnrSelf; - - // when the only node is selected, draw it - if ((n === 0) && (this.nodes.length === 1)) clone.vis = true; - } - - // shape with zero volume or without faces will not be observed - if ((clone.vol <= 0) || (clone.nfaces <= 0)) clone.vis = false; + clone.vis = false; + clone.numvischld = 1; // reset vis counter, will be filled with next scan + clone.idshift = 0; + delete clone.depth; + if (simple_copy) { + clone.vis = cloning[n].vis; + if (cloning[n].depth !== undefined) clone.depth = cloning[n].depth; if (clone.vis) res++; + continue; } - return res; - }; + var obj = this.origin[n]; - JSROOT.GEO.ClonedNodes.prototype.GetVisibleFlags = function () { - // function extract only visibility flags, used to transfer them to the worker - var res = []; - for (var n = 0; n < this.nodes.length; ++n) { - var elem = {vis: this.nodes[n].vis}; - if ('depth' in this.nodes[n]) elem.depth = this.nodes[n].depth; - res.push(elem); - } - return res; - }; - - JSROOT.GEO.ClonedNodes.prototype.ScanVisible = function (arg, vislvl) { - // Scan visible nodes in hierarchy, starting from nodeid - // Each entry in hierarchy get its unique id, which is not changed with visibility flags - - if (!this.nodes) return 0; - - if (vislvl === undefined) { - vislvl = 99999; - if (!arg) arg = {}; - arg.stack = new Array(100); // current stack - arg.nodeid = 0; - arg.counter = 0; // sequence ID of the node, used to identify it later - arg.last = 0; - arg.CopyStack = function (factor) { - var entry = { - nodeid: this.nodeid, - seqid: this.counter, - stack: (this.last > 10) ? new Int32Array(this.last) : new Array(this.last) - }; - if (factor) entry.factor = factor; // factor used to indicate importance of entry, will be build as first - for (var n = 0; n < this.last; ++n) entry.stack[n] = this.stack[n + 1]; // copy stack - return entry; - }; - - if (arg.domatrix) { - arg.matrices = []; - arg.mpool = [new THREE.Matrix4()]; // pool of Matrix objects to avoid permanent creation - arg.getmatrix = function () { - return this.matrices[this.last]; + if (clone.kind === 0) { + if (obj.fVolume) { + if (on_screen) { + clone.vis = TestBit(obj.fVolume, BITS.kVisOnScreen); + if (copy_bits) { + SetBit(obj.fVolume, BITS.kVisNone, false); + SetBit(obj.fVolume, BITS.kVisThis, clone.vis); + SetBit(obj.fVolume, BITS.kVisDaughters, true); + } + } else { + clone.vis = !TestBit(obj.fVolume, BITS.kVisNone) && + TestBit(obj.fVolume, BITS.kVisThis) && !obj.fFinder; + if (!TestBit(obj.fVolume, BITS.kVisDaughters)) + clone.depth = TestBit(obj.fVolume, BITS.kVisOneLevel) ? 1 : 0; } } + } else { + clone.vis = obj.fRnrSelf; + + // when the only node is selected, draw it + if ((n === 0) && (this.nodes.length === 1)) clone.vis = true; } - var res = 0, node = this.nodes[arg.nodeid]; + // shape with zero volume or without faces will not be observed + if ((clone.vol <= 0) || (clone.nfaces <= 0)) clone.vis = false; + + if (clone.vis) res++; + } + + return res; +}; + +ClonedNodes.prototype.GetVisibleFlags = function () { + // function extract only visibility flags, used to transfer them to the worker + var res = []; + for (var n = 0; n < this.nodes.length; ++n) { + var elem = {vis: this.nodes[n].vis}; + if ('depth' in this.nodes[n]) elem.depth = this.nodes[n].depth; + res.push(elem); + } + return res; +}; + +ClonedNodes.prototype.ScanVisible = function (arg, vislvl) { + // Scan visible nodes in hierarchy, starting from nodeid + // Each entry in hierarchy get its unique id, which is not changed with visibility flags + + if (!this.nodes) return 0; + + if (vislvl === undefined) { + vislvl = 99999; + if (!arg) arg = {}; + arg.stack = new Array(100); // current stack + arg.nodeid = 0; + arg.counter = 0; // sequence ID of the node, used to identify it later + arg.last = 0; + arg.CopyStack = function (factor) { + var entry = { + nodeid: this.nodeid, + seqid: this.counter, + stack: (this.last > 10) ? new Int32Array(this.last) : new Array(this.last) + }; + if (factor) entry.factor = factor; // factor used to indicate importance of entry, will be build as first + for (var n = 0; n < this.last; ++n) entry.stack[n] = this.stack[n + 1]; // copy stack + return entry; + }; if (arg.domatrix) { - if (!arg.mpool[arg.last + 1]) - arg.mpool[arg.last + 1] = new THREE.Matrix4(); - - var prnt = (arg.last > 0) ? arg.matrices[arg.last - 1] : new THREE.Matrix4(); - if (node.matrix) { - arg.matrices[arg.last] = arg.mpool[arg.last].fromArray(prnt.elements); - arg.matrices[arg.last].multiply(arg.mpool[arg.last + 1].fromArray(node.matrix)); - } else { - arg.matrices[arg.last] = prnt; + arg.matrices = []; + arg.mpool = [new THREE.Matrix4()]; // pool of Matrix objects to avoid permanent creation + arg.getmatrix = function () { + return this.matrices[this.last]; } } + } - if (node.vis && (vislvl >= 0)) { - if (!arg.func || arg.func(node)) res++; - } + var res = 0, node = this.nodes[arg.nodeid]; - arg.counter++; + if (arg.domatrix) { + if (!arg.mpool[arg.last + 1]) + arg.mpool[arg.last + 1] = new THREE.Matrix4(); - if ((node.depth !== undefined) && (vislvl > node.depth)) vislvl = node.depth; - - //if (arg.last > arg.stack.length - 2) - // throw 'ScanVisible: stack capacity ' + arg.stack.length + ' is not enough'; - - if (node.chlds && (node.numvischld > 0)) { - var currid = arg.counter, numvischld = 0; - arg.last++; - for (var i = 0; i < node.chlds.length; ++i) { - arg.nodeid = node.chlds[i]; - arg.stack[arg.last] = i; // in the stack one store index of child, it is path in the hierarchy - numvischld += this.ScanVisible(arg, vislvl - 1); - } - arg.last--; - res += numvischld; - if (numvischld === 0) { - node.numvischld = 0; - node.idshift = arg.counter - currid; - } + var prnt = (arg.last > 0) ? arg.matrices[arg.last - 1] : new THREE.Matrix4(); + if (node.matrix) { + arg.matrices[arg.last] = arg.mpool[arg.last].fromArray(prnt.elements); + arg.matrices[arg.last].multiply(arg.mpool[arg.last + 1].fromArray(node.matrix)); } else { - arg.counter += node.idshift; + arg.matrices[arg.last] = prnt; } + } - if (arg.last === 0) { - delete arg.last; - delete arg.stack; - delete arg.CopyStack; - delete arg.counter; - delete arg.matrices; - delete arg.mpool; - delete arg.getmatrix; + if (node.vis && (vislvl >= 0)) { + if (!arg.func || arg.func(node)) res++; + } + + arg.counter++; + + if ((node.depth !== undefined) && (vislvl > node.depth)) vislvl = node.depth; + + //if (arg.last > arg.stack.length - 2) + // throw 'ScanVisible: stack capacity ' + arg.stack.length + ' is not enough'; + + if (node.chlds && (node.numvischld > 0)) { + var currid = arg.counter, numvischld = 0; + arg.last++; + for (var i = 0; i < node.chlds.length; ++i) { + arg.nodeid = node.chlds[i]; + arg.stack[arg.last] = i; // in the stack one store index of child, it is path in the hierarchy + numvischld += this.ScanVisible(arg, vislvl - 1); } - - return res; - }; - - /** Return node name with given id. - * Either original object or description is used - * @private */ - JSROOT.GEO.ClonedNodes.prototype.GetNodeName = function (nodeid) { - if (this.origin) { - var obj = this.origin[nodeid]; - return obj ? JSROOT.GEO.ObjectName(obj) : ""; + arg.last--; + res += numvischld; + if (numvischld === 0) { + node.numvischld = 0; + node.idshift = arg.counter - currid; } - var node = this.nodes[nodeid]; - return node ? node.name : ""; - }; + } else { + arg.counter += node.idshift; + } - JSROOT.GEO.ClonedNodes.prototype.ResolveStack = function (stack, withmatrix) { + if (arg.last === 0) { + delete arg.last; + delete arg.stack; + delete arg.CopyStack; + delete arg.counter; + delete arg.matrices; + delete arg.mpool; + delete arg.getmatrix; + } - var res = {id: 0, obj: null, node: this.nodes[0], name: this.name_prefix}; + return res; +}; - // if (!this.toplevel || (this.nodes.length === 1) || (res.node.kind === 1)) res.name = ""; +/** Return node name with given id. + * Either original object or description is used + * @private */ +ClonedNodes.prototype.GetNodeName = function (nodeid) { + if (this.origin) { + var obj = this.origin[nodeid]; + return obj ? ObjectName(obj) : ""; + } + var node = this.nodes[nodeid]; + return node ? node.name : ""; +}; - if (withmatrix) { - res.matrix = new THREE.Matrix4(); - if (res.node.matrix) res.matrix.fromArray(res.node.matrix); - } +ClonedNodes.prototype.ResolveStack = function (stack, withmatrix) { - if (this.origin) - res.obj = this.origin[0]; + var res = {id: 0, obj: null, node: this.nodes[0], name: this.name_prefix}; - //if (!res.name) - // res.name = this.GetNodeName(0); + // if (!this.toplevel || (this.nodes.length === 1) || (res.node.kind === 1)) res.name = ""; - if (stack) - for (var lvl = 0; lvl < stack.length; ++lvl) { - res.id = res.node.chlds[stack[lvl]]; - res.node = this.nodes[res.id]; + if (withmatrix) { + res.matrix = new THREE.Matrix4(); + if (res.node.matrix) res.matrix.fromArray(res.node.matrix); + } - if (this.origin) - res.obj = this.origin[res.id]; + if (this.origin) + res.obj = this.origin[0]; - var subname = this.GetNodeName(res.id); - if (subname) { - if (res.name) res.name += "/"; - res.name += subname; - } - - if (withmatrix && res.node.matrix) - res.matrix.multiply(new THREE.Matrix4().fromArray(res.node.matrix)); - } - - return res; - }; - - /** Create stack array based on nodes ids array. - * Ids list should correspond to existing nodes hierarchy */ - JSROOT.GEO.ClonedNodes.prototype.MakeStackByIds = function (ids) { - var stack = []; - - if (ids[0] !== 0) { - console.error('wrong ids - first should be 0'); - return null; - } - - var node = this.nodes[0]; - - for (var k = 1; k < ids.length; ++k) { - var nodeid = ids[k]; - var chindx = node.chlds.indexOf(nodeid); - if (chindx < 0) { - console.error('wrong nodes ids ' + ids[k] + ' is not child of ' + ids[k - 1]); - return null; - } - - stack.push(chindx); - node = this.nodes[nodeid]; - } - - return stack; - }; - - /** Returns true if stack includes at any place provided nodeid */ - JSROOT.GEO.ClonedNodes.prototype.IsNodeInStack = function (nodeid, stack) { - - if (!nodeid) return true; - - var node = this.nodes[0], id = 0; + //if (!res.name) + // res.name = this.GetNodeName(0); + if (stack) for (var lvl = 0; lvl < stack.length; ++lvl) { - id = node.chlds[stack[lvl]]; - if (id == nodeid) return true; - node = this.nodes[id]; - } + res.id = res.node.chlds[stack[lvl]]; + res.node = this.nodes[res.id]; - return false; - }; + if (this.origin) + res.obj = this.origin[res.id]; - /** find stack by name which include names of all parents */ - JSROOT.GEO.ClonedNodes.prototype.FindStackByName = function (fullname) { - - var names = fullname.split('/'), currid = 0, stack = []; - - if (this.GetNodeName(currid) !== names[0]) return null; - - for (var n = 1; n < names.length; ++n) { - var node = this.nodes[currid]; - if (!node.chlds) return null; - - for (var k = 0; k < node.chlds.length; ++k) { - var chldid = node.chlds[k]; - if (this.GetNodeName(chldid) === names[n]) { - stack.push(k); - currid = chldid; - break; - } + var subname = this.GetNodeName(res.id); + if (subname) { + if (res.name) res.name += "/"; + res.name += subname; } - // no new entry - not found stack - if (stack.length === n - 1) return null; + if (withmatrix && res.node.matrix) + res.matrix.multiply(new THREE.Matrix4().fromArray(res.node.matrix)); } - return stack; - }; + return res; +}; - /** returns different properties of draw entry nodeid */ - JSROOT.GEO.ClonedNodes.prototype.getDrawEntryProperties = function (entry) { - // function return different properties for specified node - // Only if node visible, material will be created +/** Create stack array based on nodes ids array. + * Ids list should correspond to existing nodes hierarchy */ +ClonedNodes.prototype.MakeStackByIds = function (ids) { + var stack = []; - var clone = this.nodes[entry.nodeid]; - var visible = true; + if (ids[0] !== 0) { + console.error('wrong ids - first should be 0'); + return null; + } - if (clone.kind === 2) { - var prop = {name: clone.name, nname: clone.name, shape: null, material: null, chlds: null}; - var _opacity = entry.opacity; - prop.fillcolor = new THREE.Color(entry.color ? "rgb(" + entry.color + ")" : "blue"); - prop.material = new THREE.MeshLambertMaterial({ - transparent: _opacity < 1, - opacity: _opacity, wireframe: false, color: prop.fillcolor, - side: THREE.FrontSide /* THREE.DoubleSide*/, vertexColors: THREE.NoColors /*THREE.VertexColors */, - overdraw: 0., depthWrite: _opacity == 1 - }); - prop.material.inherentOpacity = _opacity; + var node = this.nodes[0]; - return prop; - } - - if (!this.origin) { - console.error('origin not there - kind', clone.kind, entry.nodeid, clone); + for (var k = 1; k < ids.length; ++k) { + var nodeid = ids[k]; + var chindx = node.chlds.indexOf(nodeid); + if (chindx < 0) { + console.error('wrong nodes ids ' + ids[k] + ' is not child of ' + ids[k - 1]); return null; } - var node = this.origin[entry.nodeid]; + stack.push(chindx); + node = this.nodes[nodeid]; + } - if (clone.kind === 1) { - // special handling for EVE nodes + return stack; +}; - var prop = { - name: JSROOT.GEO.ObjectName(node), - nname: JSROOT.GEO.ObjectName(node), - shape: node.fShape, - material: null, - chlds: null - }; +/** Returns true if stack includes at any place provided nodeid */ +ClonedNodes.prototype.IsNodeInStack = function (nodeid, stack) { - if (node.fElements !== null) prop.chlds = node.fElements.arr; + if (!nodeid) return true; - if (visible) { - var _opacity = Math.min(1, node.fRGBA[3]); - prop.fillcolor = new THREE.Color(node.fRGBA[0], node.fRGBA[1], node.fRGBA[2]); - prop.material = new THREE.MeshLambertMaterial({ - transparent: _opacity < 1, - opacity: _opacity, wireframe: false, color: prop.fillcolor, - side: THREE.FrontSide /* THREE.DoubleSide*/, vertexColors: THREE.NoColors /*THREE.VertexColors */, - overdraw: 0., depthWrite: _opacity == 1 - }); - prop.material.inherentOpacity = _opacity; + var node = this.nodes[0], id = 0; + + for (var lvl = 0; lvl < stack.length; ++lvl) { + id = node.chlds[stack[lvl]]; + if (id === nodeid) return true; + node = this.nodes[id]; + } + + return false; +}; + +/** find stack by name which include names of all parents */ +ClonedNodes.prototype.FindStackByName = function (fullname) { + + var names = fullname.split('/'), currid = 0, stack = []; + + if (this.GetNodeName(currid) !== names[0]) return null; + + for (var n = 1; n < names.length; ++n) { + var node = this.nodes[currid]; + if (!node.chlds) return null; + + for (var k = 0; k < node.chlds.length; ++k) { + var chldid = node.chlds[k]; + if (this.GetNodeName(chldid) === names[n]) { + stack.push(k); + currid = chldid; + break; } - - return prop; } - var volume = node.fVolume; + // no new entry - not found stack + if (stack.length === n - 1) return null; + } + + return stack; +}; + +function createRootColors() { + var colorMap = ['white', 'black', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan', 'rgb(89,212,84)', 'rgb(89,84,217)', 'white']; + colorMap[110] = 'white'; + + var moreCol = [ + { + col: 11, + str: 'c1b7ad4d4d4d6666668080809a9a9ab3b3b3cdcdcde6e6e6f3f3f3cdc8accdc8acc3c0a9bbb6a4b3a697b8a49cae9a8d9c8f83886657b1cfc885c3a48aa9a1839f8daebdc87b8f9a768a926983976e7b857d9ad280809caca6c0d4cf88dfbb88bd9f83c89a7dc08378cf5f61ac8f94a6787b946971d45a549300ff7b00ff6300ff4b00ff3300ff1b00ff0300ff0014ff002cff0044ff005cff0074ff008cff00a4ff00bcff00d4ff00ecff00fffd00ffe500ffcd00ffb500ff9d00ff8500ff6d00ff5500ff3d00ff2600ff0e0aff0022ff003aff0052ff006aff0082ff009aff00b1ff00c9ff00e1ff00f9ff00ffef00ffd700ffbf00ffa700ff8f00ff7700ff6000ff4800ff3000ff1800ff0000' + }, + { + col: 201, + str: '5c5c5c7b7b7bb8b8b8d7d7d78a0f0fb81414ec4848f176760f8a0f14b81448ec4876f1760f0f8a1414b84848ec7676f18a8a0fb8b814ecec48f1f1768a0f8ab814b8ec48ecf176f10f8a8a14b8b848ecec76f1f1' + }, + {col: 390, str: 'ffffcdffff9acdcd9affff66cdcd669a9a66ffff33cdcd339a9a33666633ffff00cdcd009a9a00666600333300'}, + {col: 406, str: 'cdffcd9aff9a9acd9a66ff6666cd66669a6633ff3333cd33339a3333663300ff0000cd00009a00006600003300'}, + {col: 422, str: 'cdffff9affff9acdcd66ffff66cdcd669a9a33ffff33cdcd339a9a33666600ffff00cdcd009a9a006666003333'}, + {col: 590, str: 'cdcdff9a9aff9a9acd6666ff6666cd66669a3333ff3333cd33339a3333660000ff0000cd00009a000066000033'}, + {col: 606, str: 'ffcdffff9affcd9acdff66ffcd66cd9a669aff33ffcd33cd9a339a663366ff00ffcd00cd9a009a660066330033'}, + {col: 622, str: 'ffcdcdff9a9acd9a9aff6666cd66669a6666ff3333cd33339a3333663333ff0000cd00009a0000660000330000'}, + { + col: 791, + str: 'ffcd9acd9a669a66339a6600cd9a33ffcd66ff9a00ffcd33cd9a00ffcd00ff9a33cd66006633009a3300cd6633ff9a66ff6600ff6633cd3300ff33009aff3366cd00336600339a0066cd339aff6666ff0066ff3333cd0033ff00cdff9a9acd66669a33669a009acd33cdff669aff00cdff339acd00cdff009affcd66cd9a339a66009a6633cd9a66ffcd00ff6633ffcd00cd9a00ffcd33ff9a00cd66006633009a3333cd6666ff9a00ff9a33ff6600cd3300ff339acdff669acd33669a00339a3366cd669aff0066ff3366ff0033cd0033ff339aff0066cd00336600669a339acd66cdff009aff33cdff009acd00cdffcd9aff9a66cd66339a66009a9a33cdcd66ff9a00ffcd33ff9a00cdcd00ff9a33ff6600cd33006633009a6633cd9a66ff6600ff6633ff3300cd3300ffff339acd00666600339a0033cd3366ff669aff0066ff3366cd0033ff0033ff9acdcd669a9a33669a0066cd339aff66cdff009acd009aff33cdff009a' + }, + {col: 920, str: 'cdcdcd9a9a9a666666333333'}]; + + for (var indx = 0; indx < moreCol.length; ++indx) { + var entry = moreCol[indx]; + for (var n = 0; n < entry.str.length; n += 6) { + var num = parseInt(entry.col) + parseInt(n / 6); + colorMap[num] = 'rgb(' + parseInt("0x" + entry.str.slice(n, n + 2)) + "," + parseInt("0x" + entry.str.slice(n + 2, n + 4)) + "," + parseInt("0x" + entry.str.slice(n + 4, n + 6)) + ")"; + } + } + + return colorMap; +} + +let colors = createRootColors(); + +/** returns different properties of draw entry nodeid */ +ClonedNodes.prototype.getDrawEntryProperties = function (entry) { + // function return different properties for specified node + // Only if node visible, material will be created + + var clone = this.nodes[entry.nodeid]; + var visible = true; + + if (clone.kind === 2) { + var prop = {name: clone.name, nname: clone.name, shape: null, material: null, chlds: null}; + var _opacity = entry.opacity; + prop.fillcolor = new THREE.Color(entry.color ? "rgb(" + entry.color + ")" : "blue"); + prop.material = new THREE.MeshLambertMaterial({ + transparent: _opacity < 1, + opacity: _opacity, wireframe: false, color: prop.fillcolor, + side: THREE.FrontSide /* THREE.DoubleSide*/, vertexColors: THREE.NoColors /*THREE.VertexColors */, + overdraw: 0., depthWrite: _opacity === 1 + }); + prop.material.inherentOpacity = _opacity; + + return prop; + } + + if (!this.origin) { + console.error('origin not there - kind', clone.kind, entry.nodeid, clone); + return null; + } + + var node = this.origin[entry.nodeid]; + + if (clone.kind === 1) { + // special handling for EVE nodes var prop = { - name: JSROOT.GEO.ObjectName(volume), - nname: JSROOT.GEO.ObjectName(node), - volume: node.fVolume, - shape: volume.fShape, + name: ObjectName(node), + nname: ObjectName(node), + shape: node.fShape, material: null, chlds: null }; - if (node.fVolume.fNodes !== null) prop.chlds = node.fVolume.fNodes.arr; - - if (volume) prop.linewidth = volume.fLineWidth; + if (node.fElements !== null) prop.chlds = node.fElements.arr; if (visible) { - var _opacity = 1.0; - if ((volume.fFillColor > 1) && (volume.fLineColor == 1)) - prop.fillcolor = JSROOT.Painter.root_colors[volume.fFillColor]; - else if (volume.fLineColor >= 0) - prop.fillcolor = JSROOT.Painter.root_colors[volume.fLineColor]; - - if (volume.fMedium && volume.fMedium.fMaterial) { - var fillstyle = volume.fMedium.fMaterial.fFillStyle; - var transparency = (fillstyle < 3000 || fillstyle > 3100) ? 0 : fillstyle - 3000; - if (transparency > 0) - _opacity = (100.0 - transparency) / 100.0; - if (prop.fillcolor === undefined) - prop.fillcolor = JSROOT.Painter.root_colors[volume.fMedium.fMaterial.fFillColor]; - } - if (prop.fillcolor === undefined) - prop.fillcolor = "lightgrey"; - + var _opacity = Math.min(1, node.fRGBA[3]); + prop.fillcolor = new THREE.Color(node.fRGBA[0], node.fRGBA[1], node.fRGBA[2]); prop.material = new THREE.MeshLambertMaterial({ transparent: _opacity < 1, opacity: _opacity, wireframe: false, color: prop.fillcolor, - side: THREE.FrontSide /* THREE.DoubleSide */, vertexColors: THREE.NoColors /*THREE.VertexColors*/, - overdraw: 0., depthWrite: _opacity == 1 + side: THREE.FrontSide /* THREE.DoubleSide*/, vertexColors: THREE.NoColors /*THREE.VertexColors */, + overdraw: 0., depthWrite: _opacity === 1 }); prop.material.inherentOpacity = _opacity; - } return prop; + } + + var volume = node.fVolume; + + var prop = { + name: ObjectName(volume), + nname: ObjectName(node), + volume: node.fVolume, + shape: volume.fShape, + material: null, + chlds: null }; - JSROOT.GEO.ClonedNodes.prototype.CreateObject3D = function (stack, toplevel, options) { - // create hierarchy of Object3D for given stack entry - // such hierarchy repeats hierarchy of TGeoNodes and set matrix for the objects drawing - // also set renderOrder, required to handle transparency + if (node.fVolume.fNodes !== null) prop.chlds = node.fVolume.fNodes.arr; - var node = this.nodes[0], three_prnt = toplevel, draw_depth = 0, - force = (typeof options == 'object') || (options === 'force'); + if (volume) prop.linewidth = volume.fLineWidth; - for (var lvl = 0; lvl <= stack.length; ++lvl) { - var nchld = (lvl > 0) ? stack[lvl - 1] : 0; - // extract current node - if (lvl > 0) node = this.nodes[node.chlds[nchld]]; + if (visible) { + var _opacity = 1.0; + if ((volume.fFillColor > 1) && (volume.fLineColor === 1)) + prop.fillcolor = colors[volume.fFillColor]; + else if (volume.fLineColor >= 0) + prop.fillcolor = colors[volume.fLineColor]; - var obj3d = undefined; - - if (three_prnt.children) - for (var i = 0; i < three_prnt.children.length; ++i) { - if (three_prnt.children[i].nchld === nchld) { - obj3d = three_prnt.children[i]; - break; - } - } - - if (obj3d) { - three_prnt = obj3d; - if (obj3d.$jsroot_drawable) draw_depth++; - continue; - } - - if (!force) return null; - - obj3d = new THREE.Object3D(); - - if (node.matrix) { - // console.log(stack.toString(), lvl, 'matrix ', node.matrix.toString()); - obj3d.matrix.fromArray(node.matrix); - obj3d.matrix.decompose(obj3d.position, obj3d.quaternion, obj3d.scale); - } - - // this.accountNodes(obj3d); - obj3d.nchld = nchld; // mark index to find it again later - - // add the mesh to the scene - three_prnt.add(obj3d); - - // this is only for debugging - test inversion of whole geometry - if ((lvl == 0) && (typeof options == 'object') && options.scale) { - if ((options.scale.x < 0) || (options.scale.y < 0) || (options.scale.z < 0)) { - obj3d.scale.copy(options.scale); - obj3d.updateMatrix(); - } - } - - obj3d.updateMatrixWorld(); - - three_prnt = obj3d; + if (volume.fMedium && volume.fMedium.fMaterial) { + var fillstyle = volume.fMedium.fMaterial.fFillStyle; + var transparency = (fillstyle < 3000 || fillstyle > 3100) ? 0 : fillstyle - 3000; + if (transparency > 0) + _opacity = (100.0 - transparency) / 100.0; + if (prop.fillcolor === undefined) + prop.fillcolor = colors[volume.fMedium.fMaterial.fFillColor]; } + if (prop.fillcolor === undefined) + prop.fillcolor = "lightgrey"; - if ((options === 'mesh') || (options === 'delete_mesh')) { - var mesh = null; - if (three_prnt) - for (var n = 0; (n < three_prnt.children.length) && !mesh; ++n) { - var chld = three_prnt.children[n]; - if ((chld.type === 'Mesh') && (chld.nchld === undefined)) mesh = chld; - } - - if ((options === 'mesh') || !mesh) return mesh; - - var res = three_prnt; - while (mesh && (mesh !== toplevel)) { - three_prnt = mesh.parent; - three_prnt.remove(mesh); - mesh = (three_prnt.children.length == 0) ? three_prnt : null; - } - - return res; - } - - if (three_prnt) { - three_prnt.$jsroot_drawable = true; - three_prnt.$jsroot_depth = draw_depth; - } - - return three_prnt; - }; - - JSROOT.GEO.ClonedNodes.prototype.GetVolumeBoundary = function (viscnt, facelimit, nodeslimit) { - - var result = {min: 0, max: 1, sortidcut: 0}; - - if (!this.sortmap) { - console.error('sorting map do not exist'); - return result; - } - - var maxNode, currNode, cnt = 0, facecnt = 0; - - for (var n = 0; (n < this.sortmap.length) && (cnt < nodeslimit) && (facecnt < facelimit); ++n) { - var id = this.sortmap[n]; - if (viscnt[id] === 0) continue; - currNode = this.nodes[id]; - if (!maxNode) maxNode = currNode; - cnt += viscnt[id]; - facecnt += viscnt[id] * currNode.nfaces; - } - - if (!currNode) { - console.error('no volumes selected'); - return result; - } - - // console.log('Volume boundary ' + currNode.vol + ' cnt ' + cnt + ' faces ' + facecnt); - result.max = maxNode.vol; - result.min = currNode.vol; - result.sortidcut = currNode.sortid; // latest node is not included - return result; - }; - - JSROOT.GEO.ClonedNodes.prototype.CollectVisibles = function (maxnumfaces, frustum, maxnumnodes) { - // function collects visible nodes, using maxlimit - // one can use map to define cut based on the volume or serious of cuts - - if (!maxnumnodes) maxnumnodes = maxnumfaces / 100; - - var arg = { - facecnt: 0, - viscnt: new Int32Array(this.nodes.length), // counter for each node - // nodes: this.nodes, - func: function (node) { - this.facecnt += node.nfaces; - this.viscnt[node.id]++; - return true; - } - }; - - for (var n = 0; n < arg.viscnt.length; ++n) arg.viscnt[n] = 0; - - var total = this.ScanVisible(arg), minVol = 0, maxVol = 0, camVol = -1, camFact = 10, - sortidcut = this.nodes.length + 1; - - // console.log('Total visible nodes ' + total + ' numfaces ' + arg.facecnt); - - if (arg.facecnt > maxnumfaces) { - - var bignumfaces = maxnumfaces * (frustum ? 0.8 : 1.0), - bignumnodes = maxnumnodes * (frustum ? 0.8 : 1.0); - - // define minimal volume, which always to shown - var boundary = this.GetVolumeBoundary(arg.viscnt, bignumfaces, bignumnodes); - - minVol = boundary.min; - maxVol = boundary.max; - sortidcut = boundary.sortidcut; - - if (frustum) { - arg.domatrix = true; - arg.frustum = frustum; - arg.totalcam = 0; - arg.func = function (node) { - if (node.vol <= minVol) // only small volumes are interesting - if (this.frustum.CheckShape(this.getmatrix(), node)) { - this.viscnt[node.id]++; - this.totalcam += node.nfaces; - } - - return true; - }; - - for (var n = 0; n < arg.viscnt.length; ++n) arg.viscnt[n] = 0; - - this.ScanVisible(arg); - - if (arg.totalcam > maxnumfaces * 0.2) - camVol = this.GetVolumeBoundary(arg.viscnt, maxnumfaces * 0.2, maxnumnodes * 0.2).min; - else - camVol = 0; - - camFact = maxVol / ((camVol > 0) ? (camVol > 0) : minVol); - - // console.log('Limit for camera ' + camVol + ' faces in camera view ' + arg.totalcam); - } - } - - arg.items = []; - - arg.func = function (node) { - if (node.sortid < sortidcut) { - this.items.push(this.CopyStack()); - } else if ((camVol >= 0) && (node.vol > camVol)) - if (this.frustum.CheckShape(this.getmatrix(), node)) { - this.items.push(this.CopyStack(camFact)); - } - return true; - }; - - this.ScanVisible(arg); - - return {lst: arg.items, complete: minVol === 0}; - }; - - JSROOT.GEO.ClonedNodes.prototype.MergeVisibles = function (current, prev) { - // merge list of drawn objects - // in current list we should mark if object already exists - // from previous list we should collect objects which are not there - - var indx2 = 0, del = []; - for (var indx1 = 0; (indx1 < current.length) && (indx2 < prev.length); ++indx1) { - - while ((indx2 < prev.length) && (prev[indx2].seqid < current[indx1].seqid)) { - del.push(prev[indx2++]); // this entry should be removed - } - - if ((indx2 < prev.length) && (prev[indx2].seqid === current[indx1].seqid)) { - if (prev[indx2].done) current[indx1].done = true; // copy ready flag - indx2++; - } - } - - // remove rest - while (indx2 < prev.length) - del.push(prev[indx2++]); - - return del; // - }; - - JSROOT.GEO.ClonedNodes.prototype.CollectShapes = function (lst) { - // based on list of visible nodes, collect all uniques shapes which should be build - - var shapes = []; - - for (var i = 0; i < lst.length; ++i) { - var entry = lst[i]; - var shape = this.GetNodeShape(entry.nodeid); - - if (!shape) continue; // strange, but avoid misleading - - if (shape._id === undefined) { - shape._id = shapes.length; - - shapes.push({ - id: shape._id, - shape: shape, - vol: this.nodes[entry.nodeid].vol, - refcnt: 1, - factor: 1, - ready: false - }); - - // shapes.push( { obj: shape, vol: this.nodes[entry.nodeid].vol }); - } else { - shapes[shape._id].refcnt++; - } - - entry.shape = shapes[shape._id]; // remember shape used - - // use maximal importance factor to push element to the front - if (entry.factor && (entry.factor > entry.shape.factor)) - entry.shape.factor = entry.factor; - } - - // now sort shapes in volume decrease order - shapes.sort(function (a, b) { - return b.vol * b.factor - a.vol * a.factor; + prop.material = new THREE.MeshLambertMaterial({ + transparent: _opacity < 1, + opacity: _opacity, wireframe: false, color: prop.fillcolor, + side: THREE.FrontSide /* THREE.DoubleSide */, vertexColors: THREE.NoColors /*THREE.VertexColors*/, + overdraw: 0., depthWrite: _opacity === 1 }); + prop.material.inherentOpacity = _opacity; - // now set new shape ids according to the sorted order and delete temporary field - for (var n = 0; n < shapes.length; ++n) { - var item = shapes[n]; - item.id = n; // set new ID - delete item.shape._id; // remove temporary field - } + } - // as last action set current shape id to each entry - for (var i = 0; i < lst.length; ++i) { - var entry = lst[i]; - if (entry.shape) { - entry.shapeid = entry.shape.id; // keep only id for the entry - delete entry.shape; // remove direct references - } - } + return prop; +}; - return shapes; - }; +ClonedNodes.prototype.CreateObject3D = function (stack, toplevel, options) { + // create hierarchy of Object3D for given stack entry + // such hierarchy repeats hierarchy of TGeoNodes and set matrix for the objects drawing + // also set renderOrder, required to handle transparency - JSROOT.GEO.ClonedNodes.prototype.MergeShapesLists = function (oldlst, newlst) { + var node = this.nodes[0], three_prnt = toplevel, draw_depth = 0, + force = (typeof options == 'object') || (options === 'force'); - if (!oldlst) return newlst; + for (var lvl = 0; lvl <= stack.length; ++lvl) { + var nchld = (lvl > 0) ? stack[lvl - 1] : 0; + // extract current node + if (lvl > 0) node = this.nodes[node.chlds[nchld]]; - // set geometry to shape object itself - for (var n = 0; n < oldlst.length; ++n) { - var item = oldlst[n]; + var obj3d = undefined; - item.shape._geom = item.geom; - delete item.geom; - - if (item.geomZ !== undefined) { - item.shape._geomZ = item.geomZ; - delete item.geomZ; - } - } - - // take from shape (if match) - for (var n = 0; n < newlst.length; ++n) { - var item = newlst[n]; - - if (item.shape._geom !== undefined) { - item.geom = item.shape._geom; - delete item.shape._geom; - } - - if (item.shape._geomZ !== undefined) { - item.geomZ = item.shape._geomZ; - delete item.shape._geomZ; - } - } - - // now delete all unused geometries - for (var n = 0; n < oldlst.length; ++n) { - var item = oldlst[n]; - delete item.shape._geom; - delete item.shape._geomZ; - } - - return newlst; - }; - - JSROOT.GEO.ClonedNodes.prototype.BuildShapes = function (lst, limit, timelimit) { - - var created = 0, - tm1 = new Date().getTime(), - res = {done: false, shapes: 0, faces: 0, notusedshapes: 0}; - - for (var n = 0; n < lst.length; ++n) { - var item = lst[n]; - - // if enough faces are produced, nothing else is required - if (res.done) { - item.ready = true; - continue; - } - - if (!item.ready) { - if (item.geom === undefined) { - item.geom = JSROOT.GEO.createGeometry(item.shape); - if (item.geom) created++; // indicate that at least one shape was created + if (three_prnt.children) + for (var i = 0; i < three_prnt.children.length; ++i) { + if (three_prnt.children[i].nchld === nchld) { + obj3d = three_prnt.children[i]; + break; } - item.nfaces = JSROOT.GEO.numGeometryFaces(item.geom); - item.ready = true; } - res.shapes++; - if (!item.used) res.notusedshapes++; - res.faces += item.nfaces * item.refcnt; + if (obj3d) { + three_prnt = obj3d; + if (obj3d.$jsroot_drawable) draw_depth++; + continue; + } - if (res.faces >= limit) { - res.done = true; - } else if ((created > 0.01 * lst.length) && (timelimit !== undefined)) { - var tm2 = new Date().getTime(); - if (tm2 - tm1 > timelimit) return res; + if (!force) return null; + + obj3d = new THREE.Object3D(); + + if (node.matrix) { + // console.log(stack.toString(), lvl, 'matrix ', node.matrix.toString()); + obj3d.matrix.fromArray(node.matrix); + obj3d.matrix.decompose(obj3d.position, obj3d.quaternion, obj3d.scale); + } + + // this.accountNodes(obj3d); + obj3d.nchld = nchld; // mark index to find it again later + + // add the mesh to the scene + three_prnt.add(obj3d); + + // this is only for debugging - test inversion of whole geometry + if ((lvl === 0) && (typeof options == 'object') && options.scale) { + if ((options.scale.x < 0) || (options.scale.y < 0) || (options.scale.z < 0)) { + obj3d.scale.copy(options.scale); + obj3d.updateMatrix(); } } - res.done = true; + obj3d.updateMatrixWorld(); + + three_prnt = obj3d; + } + + if ((options === 'mesh') || (options === 'delete_mesh')) { + var mesh = null; + if (three_prnt) + for (var n = 0; (n < three_prnt.children.length) && !mesh; ++n) { + var chld = three_prnt.children[n]; + if ((chld.type === 'Mesh') && (chld.nchld === undefined)) mesh = chld; + } + + if ((options === 'mesh') || !mesh) return mesh; + + var res = three_prnt; + while (mesh && (mesh !== toplevel)) { + three_prnt = mesh.parent; + three_prnt.remove(mesh); + mesh = (three_prnt.children.length === 0) ? three_prnt : null; + } return res; - }; - - JSROOT.GEO.ObjectName = function (obj) { - if (!obj || !obj.fName) return ""; - return obj.fName + (obj.$geo_suffix ? obj.$geo_suffix : ""); - }; - - JSROOT.GEO.CheckDuplicates = function (parent, chlds) { - if (parent) { - if (parent.$geo_checked) return; - parent.$geo_checked = true; - } - - var names = [], cnts = [], obj = null; - for (var k = 0; k < chlds.length; ++k) { - var chld = chlds[k]; - if (!chld || !chld.fName) continue; - if (!chld.$geo_suffix) { - var indx = names.indexOf(chld.fName); - if (indx >= 0) { - var cnt = cnts[indx] || 1; - while (names.indexOf(chld.fName + "#" + cnt) >= 0) ++cnt; - chld.$geo_suffix = "#" + cnt; - cnts[indx] = cnt + 1; - } - } - names.push(JSROOT.GEO.ObjectName(chld)); - } - }; - - JSROOT.GEO.createFlippedMesh = function (parent, shape, material) { - // when transformation matrix includes one or several inversion of axis, - // one should inverse geometry object, otherwise THREE.js cannot correctly draw it - - var flip = new THREE.Vector3(1, 1, -1); - - if (shape.geomZ === undefined) { - - if (shape.geom.type == 'BufferGeometry') { - - var pos = shape.geom.getAttribute('position').array, - norm = shape.geom.getAttribute('normal').array; - - var index = shape.geom.getIndex(); - - if (index) { - // we need to unfold all points to - var arr = index.array, - i0 = shape.geom.drawRange.start, - ilen = shape.geom.drawRange.count; - if (i0 + ilen > arr.length) ilen = arr.length - i0; - - var dpos = new Float32Array(ilen * 3), dnorm = new Float32Array(ilen * 3); - for (var ii = 0; ii < ilen; ++ii) { - var k = arr[i0 + ii]; - if ((k < 0) || (k * 3 >= pos.length)) console.log('strange index', k * 3, pos.length); - dpos[ii * 3] = pos[k * 3]; - dpos[ii * 3 + 1] = pos[k * 3 + 1]; - dpos[ii * 3 + 2] = pos[k * 3 + 2]; - dnorm[ii * 3] = norm[k * 3]; - dnorm[ii * 3 + 1] = norm[k * 3 + 1]; - dnorm[ii * 3 + 2] = norm[k * 3 + 2]; - } - - pos = dpos; - norm = dnorm; - } - - var len = pos.length, n, shift = 0, - newpos = new Float32Array(len), - newnorm = new Float32Array(len); - - // we should swap second and third point in each face - for (n = 0; n < len; n += 3) { - newpos[n] = pos[n + shift]; - newpos[n + 1] = pos[n + 1 + shift]; - newpos[n + 2] = -pos[n + 2 + shift]; - - newnorm[n] = norm[n + shift]; - newnorm[n + 1] = norm[n + 1 + shift]; - newnorm[n + 2] = -norm[n + 2 + shift]; - - shift += 3; - if (shift === 6) shift = -3; // values 0,3,-3 - } - - shape.geomZ = new THREE.BufferGeometry(); - shape.geomZ.addAttribute('position', new THREE.BufferAttribute(newpos, 3)); - shape.geomZ.addAttribute('normal', new THREE.BufferAttribute(newnorm, 3)); - // normals are calculated with normal geometry and correctly scaled - // geom.computeVertexNormals(); - - } else { - - shape.geomZ = shape.geom.clone(); - - shape.geomZ.scale(flip.x, flip.y, flip.z); - - var face, d, n = 0; - while (n < shape.geomZ.faces.length) { - face = geom.faces[n++]; - d = face.b; - face.b = face.c; - face.c = d; - } - - // normals are calculated with normal geometry and correctly scaled - // geom.computeFaceNormals(); - } - } - - var mesh = new THREE.Mesh(shape.geomZ, material); - mesh.scale.copy(flip); - mesh.updateMatrix(); - - mesh._flippedMesh = true; - - return mesh; - }; - - /** Cleanup shape entity - * @private */ - JSROOT.GEO.cleanupShape = function (shape) { - if (!shape) return; - - if (shape.geom && (typeof shape.geom.dispose == 'funciton')) - shape.geom.dispose(); - - if (shape.geomZ && (typeof shape.geomZ.dispose == 'funciton')) - shape.geomZ.dispose(); - - delete shape.geom; - delete shape.geomZ; - }; - - JSROOT.GEO.produceRenderOrder = function (toplevel, origin, method, clones) { - // function scans throug hierarchy of objects and try to set renderOrder - // algorithm is not perfect, but better then nothing - - var raycast = new THREE.Raycaster(); - - function setdefaults(top) { - if (!top) return; - top.traverse(function (obj) { - obj.renderOrder = 0; - if (obj.material) obj.material.depthWrite = true; // by default depthWriting enabled - }); - } - - function traverse(obj, lvl, arr) { - // traverse hierarchy and extract all children of given level - // if (obj.$jsroot_depth===undefined) return; - - if (!obj.children) return; - - for (var k = 0; k < obj.children.length; ++k) { - var chld = obj.children[k]; - if (chld.$jsroot_order === lvl) { - if (chld.material) { - if (chld.material.transparent) { - chld.material.depthWrite = false; // disable depth writing for transparent - arr.push(chld); - } else { - setdefaults(chld); - } - } - } else if ((obj.$jsroot_depth === undefined) || (obj.$jsroot_depth < lvl)) traverse(chld, lvl, arr); - } - } - - function sort(arr, minorder, maxorder) { - // resort meshes using ray caster and camera position - // idea to identify meshes which are in front or behind - - if (arr.length > 300) { - // too many of them, just set basic level and exit - for (var i = 0; i < arr.length; ++i) arr[i].renderOrder = (minorder + maxorder) / 2; - return false; - } - - // first calculate distance to the camera - // it gives preliminary order of volumes - - for (var i = 0; i < arr.length; ++i) { - var mesh = arr[i], - box3 = mesh.$jsroot_box3; - - if (!box3) - mesh.$jsroot_box3 = box3 = JSROOT.GEO.getBoundingBox(mesh); - - if (method === 'size') { - mesh.$jsroot_distance = box3.getSize(new THREE.Vector3()); - continue; - } - - if (method === "pnt") { - mesh.$jsroot_distance = origin.distanceTo(box3.getCenter()); - continue; - } - - var dist = Math.min(dist, origin.distanceTo(box3.min), origin.distanceTo(box3.max)); - - var pnt = new THREE.Vector3(box3.min.x, box3.min.y, box3.max.z); - dist = Math.min(dist, origin.distanceTo(pnt)); - pnt.set(box3.min.x, box3.max.y, box3.min.z); - dist = Math.min(dist, origin.distanceTo(pnt)); - pnt.set(box3.max.x, box3.min.y, box3.min.z); - dist = Math.min(dist, origin.distanceTo(pnt)); - - pnt.set(box3.max.x, box3.max.y, box3.min.z); - dist = Math.min(dist, origin.distanceTo(pnt)); - - pnt.set(box3.max.x, box3.min.y, box3.max.z); - dist = Math.min(dist, origin.distanceTo(pnt)); - - pnt.set(box3.min.x, box3.max.y, box3.max.z); - dist = Math.min(dist, origin.distanceTo(pnt)); - - mesh.$jsroot_distance = dist; - } - - arr.sort(function (a, b) { - return a.$jsroot_distance - b.$jsroot_distance; - }); - - var resort = new Array(arr.length); - - for (var i = 0; i < arr.length; ++i) { - arr[i].$jsroot_index = i; - resort[i] = arr[i]; - } - - if (method === "ray") - for (var i = arr.length - 1; i >= 0; --i) { - var mesh = arr[i], - box3 = mesh.$jsroot_box3, - direction = box3.getCenter(); - - for (var ntry = 0; ntry < 2; ++ntry) { - - direction.sub(origin).normalize(); - - raycast.set(origin, direction); - - var intersects = raycast.intersectObjects(arr, false); // only plain array - - var unique = []; - - for (var k1 = 0; k1 < intersects.length; ++k1) { - if (unique.indexOf(intersects[k1].object) < 0) unique.push(intersects[k1].object); - // if (intersects[k1].object === mesh) break; // trace until object itself - } - - intersects = unique; - - if ((intersects.indexOf(mesh) < 0) && (ntry > 0)) - console.log('MISS', clones ? clones.ResolveStack(mesh.stack).name : "???"); - - if ((intersects.indexOf(mesh) >= 0) || (ntry > 0)) break; - - var pos = mesh.geometry.attributes.position.array; - - direction = new THREE.Vector3((pos[0] + pos[3] + pos[6]) / 3, (pos[1] + pos[4] + pos[7]) / 3, (pos[2] + pos[5] + pos[8]) / 3); - - direction.applyMatrix4(mesh.matrixWorld); - } - - // now push first object in intersects to the front - for (var k1 = 0; k1 < intersects.length - 1; ++k1) { - var mesh1 = intersects[k1], mesh2 = intersects[k1 + 1], - i1 = mesh1.$jsroot_index, i2 = mesh2.$jsroot_index; - if (i1 < i2) continue; - for (var ii = i2; ii < i1; ++ii) { - resort[ii] = resort[ii + 1]; - resort[ii].$jsroot_index = ii; - } - resort[i1] = mesh2; - mesh2.$jsroot_index = i1; - } - - } - - for (var i = 0; i < resort.length; ++i) { - resort[i].renderOrder = maxorder - (i + 1) / (resort.length + 1) * (maxorder - minorder); - delete resort[i].$jsroot_index; - delete resort[i].$jsroot_distance; - } - + } + + if (three_prnt) { + three_prnt.$jsroot_drawable = true; + three_prnt.$jsroot_depth = draw_depth; + } + + return three_prnt; +}; + +ClonedNodes.prototype.GetVolumeBoundary = function (viscnt, facelimit, nodeslimit) { + + var result = {min: 0, max: 1, sortidcut: 0}; + + if (!this.sortmap) { + console.error('sorting map do not exist'); + return result; + } + + var maxNode, currNode, cnt = 0, facecnt = 0; + + for (var n = 0; (n < this.sortmap.length) && (cnt < nodeslimit) && (facecnt < facelimit); ++n) { + var id = this.sortmap[n]; + if (viscnt[id] === 0) continue; + currNode = this.nodes[id]; + if (!maxNode) maxNode = currNode; + cnt += viscnt[id]; + facecnt += viscnt[id] * currNode.nfaces; + } + + if (!currNode) { + console.error('no volumes selected'); + return result; + } + + // console.log('Volume boundary ' + currNode.vol + ' cnt ' + cnt + ' faces ' + facecnt); + result.max = maxNode.vol; + result.min = currNode.vol; + result.sortidcut = currNode.sortid; // latest node is not included + return result; +}; + +ClonedNodes.prototype.CollectVisibles = function (maxnumfaces, frustum, maxnumnodes) { + // function collects visible nodes, using maxlimit + // one can use map to define cut based on the volume or serious of cuts + + if (!maxnumnodes) maxnumnodes = maxnumfaces / 100; + + var arg = { + facecnt: 0, + viscnt: new Int32Array(this.nodes.length), // counter for each node + // nodes: this.nodes, + func: function (node) { + this.facecnt += node.nfaces; + this.viscnt[node.id]++; return true; } - - function process(obj, lvl, minorder, maxorder) { - var arr = [], did_sort = false; - - traverse(obj, lvl, arr); - - if (!arr.length) return; - - if (minorder === maxorder) { - for (var k = 0; k < arr.length; ++k) - arr[k].renderOrder = minorder; - } else { - did_sort = sort(arr, minorder, maxorder); - if (!did_sort) minorder = maxorder = (minorder + maxorder) / 2; - } - - for (var k = 0; k < arr.length; ++k) { - var next = arr[k].parent, min = minorder, max = maxorder; - - if (did_sort) { - max = arr[k].renderOrder; - min = max - (maxorder - minorder) / (arr.length + 2); - } - - process(next, lvl + 1, min, max); - } - } - - if (!method || (method === "dflt")) - setdefaults(toplevel); - else - process(toplevel, 0, 1, 1000000); }; - JSROOT.GEO.build = function (obj, opt) { - // function can be used to build three.js model for TGeo object + for (var n = 0; n < arg.viscnt.length; ++n) arg.viscnt[n] = 0; - if (!obj) return; + var total = this.ScanVisible(arg), minVol = 0, maxVol = 0, camVol = -1, camFact = 10, + sortidcut = this.nodes.length + 1; - if (!opt) opt = {}; - if (!opt.numfaces) opt.numfaces = 100000; - if (!opt.numnodes) opt.numnodes = 1000; + // console.log('Total visible nodes ' + total + ' numfaces ' + arg.facecnt); - opt.res_mesh = opt.res_faces = 0; + if (arg.facecnt > maxnumfaces) { - var shape = null; + var bignumfaces = maxnumfaces * (frustum ? 0.8 : 1.0), + bignumnodes = maxnumnodes * (frustum ? 0.8 : 1.0); - if (('fShapeBits' in obj) && ('fShapeId' in obj)) { - shape = obj; - obj = null; - } else if ((obj._typename === 'TGeoVolumeAssembly') || (obj._typename === 'TGeoVolume')) { - shape = obj.fShape; - } else if ((obj._typename === "TEveGeoShapeExtract") || (obj._typename === "ROOT::Experimental::TEveGeoShapeExtract")) { - shape = obj.fShape; - } else if (obj._typename === 'TGeoManager') { - obj = obj.fMasterVolume; - JSROOT.GEO.SetBit(obj, JSROOT.GEO.BITS.kVisThis, false); - shape = obj.fShape; - } else if ('fVolume' in obj) { - if (obj.fVolume) shape = obj.fVolume.fShape; - } else { - obj = null; + // define minimal volume, which always to shown + var boundary = this.GetVolumeBoundary(arg.viscnt, bignumfaces, bignumnodes); + + minVol = boundary.min; + maxVol = boundary.max; + sortidcut = boundary.sortidcut; + + if (frustum) { + arg.domatrix = true; + arg.frustum = frustum; + arg.totalcam = 0; + arg.func = function (node) { + if (node.vol <= minVol) // only small volumes are interesting + if (this.frustum.CheckShape(this.getmatrix(), node)) { + this.viscnt[node.id]++; + this.totalcam += node.nfaces; + } + + return true; + }; + + for (var n = 0; n < arg.viscnt.length; ++n) arg.viscnt[n] = 0; + + this.ScanVisible(arg); + + if (arg.totalcam > maxnumfaces * 0.2) + camVol = this.GetVolumeBoundary(arg.viscnt, maxnumfaces * 0.2, maxnumnodes * 0.2).min; + else + camVol = 0; + + camFact = maxVol / ((camVol > 0) ? (camVol > 0) : minVol); + + // console.log('Limit for camera ' + camVol + ' faces in camera view ' + arg.totalcam); + } + } + + arg.items = []; + + arg.func = function (node) { + if (node.sortid < sortidcut) { + this.items.push(this.CopyStack()); + } else if ((camVol >= 0) && (node.vol > camVol)) + if (this.frustum.CheckShape(this.getmatrix(), node)) { + this.items.push(this.CopyStack(camFact)); + } + return true; + }; + + this.ScanVisible(arg); + + return {lst: arg.items, complete: minVol === 0}; +}; + +ClonedNodes.prototype.MergeVisibles = function (current, prev) { + // merge list of drawn objects + // in current list we should mark if object already exists + // from previous list we should collect objects which are not there + + var indx2 = 0, del = []; + for (var indx1 = 0; (indx1 < current.length) && (indx2 < prev.length); ++indx1) { + + while ((indx2 < prev.length) && (prev[indx2].seqid < current[indx1].seqid)) { + del.push(prev[indx2++]); // this entry should be removed } - if (opt.composite && shape && (shape._typename === 'TGeoCompositeShape') && shape.fNode) - obj = JSROOT.GEO.buildCompositeVolume(shape); + if ((indx2 < prev.length) && (prev[indx2].seqid === current[indx1].seqid)) { + if (prev[indx2].done) current[indx1].done = true; // copy ready flag + indx2++; + } + } - if (!obj && shape) - obj = JSROOT.extend(JSROOT.Create("TEveGeoShapeExtract"), - {fTrans: null, fShape: shape, fRGBA: [0, 1, 0, 1], fElements: null, fRnrSelf: true}); + // remove rest + while (indx2 < prev.length) + del.push(prev[indx2++]); - if (!obj) return null; + return del; // +}; - if (obj._typename.indexOf('TGeoVolume') === 0) - obj = {_typename: "TGeoNode", fVolume: obj, fName: obj.fName, $geoh: obj.$geoh, _proxy: true}; +ClonedNodes.prototype.CollectShapes = function (lst) { + // based on list of visible nodes, collect all uniques shapes which should be build - var clones = new JSROOT.GEO.ClonedNodes(obj); + var shapes = []; - var uniquevis = clones.MarkVisisble(true); + for (var i = 0; i < lst.length; ++i) { + var entry = lst[i]; + var shape = this.GetNodeShape(entry.nodeid); - if (uniquevis <= 0) - uniquevis = clones.MarkVisisble(false); - else - uniquevis = clones.MarkVisisble(true, true); // copy bits once and use normal visibility bits + if (!shape) continue; // strange, but avoid misleading - var numvis = clones.MarkVisisble(); + if (shape._id === undefined) { + shape._id = shapes.length; - var frustum = null; + shapes.push({ + id: shape._id, + shape: shape, + vol: this.nodes[entry.nodeid].vol, + refcnt: 1, + factor: 1, + ready: false + }); - // collect visible nodes - var res = clones.CollectVisibles(opt.numfaces, frustum, opt.numnodes); + // shapes.push( { obj: shape, vol: this.nodes[entry.nodeid].vol }); + } else { + shapes[shape._id].refcnt++; + } - var draw_nodes = res.lst; + entry.shape = shapes[shape._id]; // remember shape used - // collect shapes - var shapes = clones.CollectShapes(draw_nodes); + // use maximal importance factor to push element to the front + if (entry.factor && (entry.factor > entry.shape.factor)) + entry.shape.factor = entry.factor; + } - clones.BuildShapes(shapes, opt.numfaces); + // now sort shapes in volume decrease order + shapes.sort(function (a, b) { + return b.vol * b.factor - a.vol * a.factor; + }); - var toplevel = new THREE.Object3D(); + // now set new shape ids according to the sorted order and delete temporary field + for (var n = 0; n < shapes.length; ++n) { + var item = shapes[n]; + item.id = n; // set new ID + delete item.shape._id; // remove temporary field + } - for (var n = 0; n < draw_nodes.length; ++n) { - var entry = draw_nodes[n]; - if (entry.done) continue; + // as last action set current shape id to each entry + for (var i = 0; i < lst.length; ++i) { + var entry = lst[i]; + if (entry.shape) { + entry.shapeid = entry.shape.id; // keep only id for the entry + delete entry.shape; // remove direct references + } + } - var shape = shapes[entry.shapeid]; - if (!shape.ready) { - console.warn('shape marked as not ready when should'); - break; + return shapes; +}; + +ClonedNodes.prototype.MergeShapesLists = function (oldlst, newlst) { + + if (!oldlst) return newlst; + + // set geometry to shape object itself + for (var n = 0; n < oldlst.length; ++n) { + var item = oldlst[n]; + + item.shape._geom = item.geom; + delete item.geom; + + if (item.geomZ !== undefined) { + item.shape._geomZ = item.geomZ; + delete item.geomZ; + } + } + + // take from shape (if match) + for (var n = 0; n < newlst.length; ++n) { + var item = newlst[n]; + + if (item.shape._geom !== undefined) { + item.geom = item.shape._geom; + delete item.shape._geom; + } + + if (item.shape._geomZ !== undefined) { + item.geomZ = item.shape._geomZ; + delete item.shape._geomZ; + } + } + + // now delete all unused geometries + for (var n = 0; n < oldlst.length; ++n) { + var item = oldlst[n]; + delete item.shape._geom; + delete item.shape._geomZ; + } + + return newlst; +}; + +ClonedNodes.prototype.BuildShapes = function (lst, limit, timelimit) { + + var created = 0, + tm1 = new Date().getTime(), + res = {done: false, shapes: 0, faces: 0, notusedshapes: 0}; + + for (var n = 0; n < lst.length; ++n) { + var item = lst[n]; + + // if enough faces are produced, nothing else is required + if (res.done) { + item.ready = true; + continue; + } + + if (!item.ready) { + if (item.geom === undefined) { + item.geom = createGeometry(item.shape); + if (item.geom) created++; // indicate that at least one shape was created } - entry.done = true; - shape.used = true; // indicate that shape was used in building + item.nfaces = numGeometryFaces(item.geom); + item.ready = true; + } - if (!shape.geom || (shape.nfaces === 0)) { - // node is visible, but shape does not created - clones.CreateObject3D(entry.stack, toplevel, 'delete_mesh'); + res.shapes++; + if (!item.used) res.notusedshapes++; + res.faces += item.nfaces * item.refcnt; + + if (res.faces >= limit) { + res.done = true; + } else if ((created > 0.01 * lst.length) && (timelimit !== undefined)) { + var tm2 = new Date().getTime(); + if (tm2 - tm1 > timelimit) return res; + } + } + + res.done = true; + + return res; +}; + +function ObjectName(obj) { + if (!obj || !obj.fName) return ""; + return obj.fName + (obj.$geo_suffix ? obj.$geo_suffix : ""); +} + +CheckDuplicates = CheckDuplicates; + +function CheckDuplicates(parent, chlds) { + if (parent) { + if (parent.$geo_checked) return; + parent.$geo_checked = true; + } + + var names = [], cnts = [], obj = null; + for (var k = 0; k < chlds.length; ++k) { + var chld = chlds[k]; + if (!chld || !chld.fName) continue; + if (!chld.$geo_suffix) { + var indx = names.indexOf(chld.fName); + if (indx >= 0) { + var cnt = cnts[indx] || 1; + while (names.indexOf(chld.fName + "#" + cnt) >= 0) ++cnt; + chld.$geo_suffix = "#" + cnt; + cnts[indx] = cnt + 1; + } + } + names.push(ObjectName(chld)); + } +} + +function createFlippedMesh(parent, shape, material) { + // when transformation matrix includes one or several inversion of axis, + // one should inverse geometry object, otherwise THREE.js cannot correctly draw it + + var flip = new THREE.Vector3(1, 1, -1); + + if (shape.geomZ === undefined) { + + if (shape.geom.type === 'BufferGeometry') { + + var pos = shape.geom.getAttribute('position').array, + norm = shape.geom.getAttribute('normal').array; + + var index = shape.geom.getIndex(); + + if (index) { + // we need to unfold all points to + var arr = index.array, + i0 = shape.geom.drawRange.start, + ilen = shape.geom.drawRange.count; + if (i0 + ilen > arr.length) ilen = arr.length - i0; + + var dpos = new Float32Array(ilen * 3), dnorm = new Float32Array(ilen * 3); + for (var ii = 0; ii < ilen; ++ii) { + var k = arr[i0 + ii]; + if ((k < 0) || (k * 3 >= pos.length)) console.log('strange index', k * 3, pos.length); + dpos[ii * 3] = pos[k * 3]; + dpos[ii * 3 + 1] = pos[k * 3 + 1]; + dpos[ii * 3 + 2] = pos[k * 3 + 2]; + dnorm[ii * 3] = norm[k * 3]; + dnorm[ii * 3 + 1] = norm[k * 3 + 1]; + dnorm[ii * 3 + 2] = norm[k * 3 + 2]; + } + + pos = dpos; + norm = dnorm; + } + + var len = pos.length, n, shift = 0, + newpos = new Float32Array(len), + newnorm = new Float32Array(len); + + // we should swap second and third point in each face + for (n = 0; n < len; n += 3) { + newpos[n] = pos[n + shift]; + newpos[n + 1] = pos[n + 1 + shift]; + newpos[n + 2] = -pos[n + 2 + shift]; + + newnorm[n] = norm[n + shift]; + newnorm[n + 1] = norm[n + 1 + shift]; + newnorm[n + 2] = -norm[n + 2 + shift]; + + shift += 3; + if (shift === 6) shift = -3; // values 0,3,-3 + } + + shape.geomZ = new THREE.BufferGeometry(); + shape.geomZ.addAttribute('position', new THREE.BufferAttribute(newpos, 3)); + shape.geomZ.addAttribute('normal', new THREE.BufferAttribute(newnorm, 3)); + // normals are calculated with normal geometry and correctly scaled + // geom.computeVertexNormals(); + + } else { + + shape.geomZ = shape.geom.clone(); + + shape.geomZ.scale(flip.x, flip.y, flip.z); + + var face, d, n = 0; + while (n < shape.geomZ.faces.length) { + face = geom.faces[n++]; + d = face.b; + face.b = face.c; + face.c = d; + } + + // normals are calculated with normal geometry and correctly scaled + // geom.computeFaceNormals(); + } + } + + var mesh = new THREE.Mesh(shape.geomZ, material); + mesh.scale.copy(flip); + mesh.updateMatrix(); + + mesh._flippedMesh = true; + + return mesh; +} + +/** Cleanup shape entity + * @private */ +function cleanupShape(shape) { + if (!shape) return; + + if (shape.geom && (typeof shape.geom.dispose == 'funciton')) + shape.geom.dispose(); + + if (shape.geomZ && (typeof shape.geomZ.dispose == 'funciton')) + shape.geomZ.dispose(); + + delete shape.geom; + delete shape.geomZ; +} + +function produceRenderOrder(toplevel, origin, method, clones) { + // function scans throug hierarchy of objects and try to set renderOrder + // algorithm is not perfect, but better then nothing + + var raycast = new THREE.Raycaster(); + + function setdefaults(top) { + if (!top) return; + top.traverse(function (obj) { + obj.renderOrder = 0; + if (obj.material) obj.material.depthWrite = true; // by default depthWriting enabled + }); + } + + function traverse(obj, lvl, arr) { + // traverse hierarchy and extract all children of given level + // if (obj.$jsroot_depth===undefined) return; + + if (!obj.children) return; + + for (var k = 0; k < obj.children.length; ++k) { + var chld = obj.children[k]; + if (chld.$jsroot_order === lvl) { + if (chld.material) { + if (chld.material.transparent) { + chld.material.depthWrite = false; // disable depth writing for transparent + arr.push(chld); + } else { + setdefaults(chld); + } + } + } else if ((obj.$jsroot_depth === undefined) || (obj.$jsroot_depth < lvl)) traverse(chld, lvl, arr); + } + } + + function sort(arr, minorder, maxorder) { + // resort meshes using ray caster and camera position + // idea to identify meshes which are in front or behind + + if (arr.length > 300) { + // too many of them, just set basic level and exit + for (var i = 0; i < arr.length; ++i) arr[i].renderOrder = (minorder + maxorder) / 2; + return false; + } + + // first calculate distance to the camera + // it gives preliminary order of volumes + + for (var i = 0; i < arr.length; ++i) { + var mesh = arr[i], + box3 = mesh.$jsroot_box3; + + if (!box3) + mesh.$jsroot_box3 = box3 = getBoundingBox(mesh); + + if (method === 'size') { + mesh.$jsroot_distance = box3.getSize(new THREE.Vector3()); continue; } - var prop = clones.getDrawEntryProperties(entry); - - opt.res_mesh++; - opt.res_faces += shape.nfaces; - - var obj3d = clones.CreateObject3D(entry.stack, toplevel, opt); - - prop.material.wireframe = opt.wireframe; - - prop.material.side = opt.doubleside ? THREE.DoubleSide : THREE.FrontSide; - - var mesh = null; - - if (obj3d.matrixWorld.determinant() > -0.9) { - mesh = new THREE.Mesh(shape.geom, prop.material); - } else { - mesh = JSROOT.GEO.createFlippedMesh(obj3d, shape, prop.material); + if (method === "pnt") { + mesh.$jsroot_distance = origin.distanceTo(box3.getCenter()); + continue; } - obj3d.add(mesh); - // specify rendering order, required for transparency handling - //if (obj3d.$jsroot_depth !== undefined) - // mesh.renderOrder = clones.maxdepth - obj3d.$jsroot_depth; - //else - // mesh.renderOrder = clones.maxdepth - entry.stack.length; + var dist = Math.min(dist, origin.distanceTo(box3.min), origin.distanceTo(box3.max)); + + var pnt = new THREE.Vector3(box3.min.x, box3.min.y, box3.max.z); + dist = Math.min(dist, origin.distanceTo(pnt)); + pnt.set(box3.min.x, box3.max.y, box3.min.z); + dist = Math.min(dist, origin.distanceTo(pnt)); + pnt.set(box3.max.x, box3.min.y, box3.min.z); + dist = Math.min(dist, origin.distanceTo(pnt)); + + pnt.set(box3.max.x, box3.max.y, box3.min.z); + dist = Math.min(dist, origin.distanceTo(pnt)); + + pnt.set(box3.max.x, box3.min.y, box3.max.z); + dist = Math.min(dist, origin.distanceTo(pnt)); + + pnt.set(box3.min.x, box3.max.y, box3.max.z); + dist = Math.min(dist, origin.distanceTo(pnt)); + + mesh.$jsroot_distance = dist; } - //JSROOT.CallBack(call_back, toplevel); + arr.sort(function (a, b) { + return a.$jsroot_distance - b.$jsroot_distance; + }); - return toplevel; - }; + var resort = new Array(arr.length); - JSROOT.GEO.getBoundingBox = function (node, box3) { - - // extract code of Box3.expandByObject - // Major difference - do not traverse hierarchy - - if (!node || !node.geometry) return box3; - - if (!box3) { - box3 = new THREE.Box3(); - box3.makeEmpty(); + for (var i = 0; i < arr.length; ++i) { + arr[i].$jsroot_index = i; + resort[i] = arr[i]; } - node.updateMatrixWorld(); + if (method === "ray") + for (var i = arr.length - 1; i >= 0; --i) { + var mesh = arr[i], + box3 = mesh.$jsroot_box3, + direction = box3.getCenter(); - var v1 = new THREE.Vector3(), - geometry = node.geometry; + for (var ntry = 0; ntry < 2; ++ntry) { - if (geometry.isGeometry) { - var vertices = geometry.vertices; - for (var i = 0, l = vertices.length; i < l; i++) { - v1.copy(vertices[i]); - v1.applyMatrix4(node.matrixWorld); + direction.sub(origin).normalize(); + + raycast.set(origin, direction); + + var intersects = raycast.intersectObjects(arr, false); // only plain array + + var unique = []; + + for (var k1 = 0; k1 < intersects.length; ++k1) { + if (unique.indexOf(intersects[k1].object) < 0) unique.push(intersects[k1].object); + // if (intersects[k1].object === mesh) break; // trace until object itself + } + + intersects = unique; + + if ((intersects.indexOf(mesh) < 0) && (ntry > 0)) + console.log('MISS', clones ? clones.ResolveStack(mesh.stack).name : "???"); + + if ((intersects.indexOf(mesh) >= 0) || (ntry > 0)) break; + + var pos = mesh.geometry.attributes.position.array; + + direction = new THREE.Vector3((pos[0] + pos[3] + pos[6]) / 3, (pos[1] + pos[4] + pos[7]) / 3, (pos[2] + pos[5] + pos[8]) / 3); + + direction.applyMatrix4(mesh.matrixWorld); + } + + // now push first object in intersects to the front + for (var k1 = 0; k1 < intersects.length - 1; ++k1) { + var mesh1 = intersects[k1], mesh2 = intersects[k1 + 1], + i1 = mesh1.$jsroot_index, i2 = mesh2.$jsroot_index; + if (i1 < i2) continue; + for (var ii = i2; ii < i1; ++ii) { + resort[ii] = resort[ii + 1]; + resort[ii].$jsroot_index = ii; + } + resort[i1] = mesh2; + mesh2.$jsroot_index = i1; + } + + } + + for (var i = 0; i < resort.length; ++i) { + resort[i].renderOrder = maxorder - (i + 1) / (resort.length + 1) * (maxorder - minorder); + delete resort[i].$jsroot_index; + delete resort[i].$jsroot_distance; + } + + return true; + } + + function process(obj, lvl, minorder, maxorder) { + var arr = [], did_sort = false; + + traverse(obj, lvl, arr); + + if (!arr.length) return; + + if (minorder === maxorder) { + for (var k = 0; k < arr.length; ++k) + arr[k].renderOrder = minorder; + } else { + did_sort = sort(arr, minorder, maxorder); + if (!did_sort) minorder = maxorder = (minorder + maxorder) / 2; + } + + for (var k = 0; k < arr.length; ++k) { + var next = arr[k].parent, min = minorder, max = maxorder; + + if (did_sort) { + max = arr[k].renderOrder; + min = max - (maxorder - minorder) / (arr.length + 2); + } + + process(next, lvl + 1, min, max); + } + } + + if (!method || (method === "dflt")) + setdefaults(toplevel); + else + process(toplevel, 0, 1, 1000000); +} + +export function build(obj, opt) { + // function can be used to build three.js model for TGeo object + + if (!obj) return; + + if (!opt) opt = {}; + if (!opt.numfaces) opt.numfaces = 100000; + if (!opt.numnodes) opt.numnodes = 1000; + + opt.res_mesh = opt.res_faces = 0; + + var shape = null; + + if (('fShapeBits' in obj) && ('fShapeId' in obj)) { + shape = obj; + obj = null; + } else if ((obj._typename === 'TGeoVolumeAssembly') || (obj._typename === 'TGeoVolume')) { + shape = obj.fShape; + } else if ((obj._typename === "TEveGeoShapeExtract") || (obj._typename === "ROOT::Experimental::TEveGeoShapeExtract")) { + shape = obj.fShape; + } else if (obj._typename === 'TGeoManager') { + obj = obj.fMasterVolume; + SetBit(obj, BITS.kVisThis, false); + shape = obj.fShape; + } else if ('fVolume' in obj) { + if (obj.fVolume) shape = obj.fVolume.fShape; + } else { + obj = null; + } + + if (opt.composite && shape && (shape._typename === 'TGeoCompositeShape') && shape.fNode) + obj = buildCompositeVolume(shape); + + // if (!obj && shape) + // obj = extend(JSROOT.Create("TEveGeoShapeExtract"), + // {fTrans: null, fShape: shape, fRGBA: [0, 1, 0, 1], fElements: null, fRnrSelf: true}); + + if (!obj) return null; + + if (obj._typename.indexOf('TGeoVolume') === 0) + obj = {_typename: "TGeoNode", fVolume: obj, fName: obj.fName, $geoh: obj.$geoh, _proxy: true}; + + var clones = new ClonedNodes(obj); + + var uniquevis = clones.MarkVisisble(true); + + if (uniquevis <= 0) + uniquevis = clones.MarkVisisble(false); + else + uniquevis = clones.MarkVisisble(true, true); // copy bits once and use normal visibility bits + + var numvis = clones.MarkVisisble(); + + var frustum = null; + + // collect visible nodes + var res = clones.CollectVisibles(opt.numfaces, frustum, opt.numnodes); + + var draw_nodes = res.lst; + + // collect shapes + var shapes = clones.CollectShapes(draw_nodes); + + clones.BuildShapes(shapes, opt.numfaces); + + var toplevel = new THREE.Object3D(); + + for (var n = 0; n < draw_nodes.length; ++n) { + var entry = draw_nodes[n]; + if (entry.done) continue; + + var shape = shapes[entry.shapeid]; + if (!shape.ready) { + console.warn('shape marked as not ready when should'); + break; + } + entry.done = true; + shape.used = true; // indicate that shape was used in building + + if (!shape.geom || (shape.nfaces === 0)) { + // node is visible, but shape does not created + clones.CreateObject3D(entry.stack, toplevel, 'delete_mesh'); + continue; + } + + var prop = clones.getDrawEntryProperties(entry); + + opt.res_mesh++; + opt.res_faces += shape.nfaces; + + var obj3d = clones.CreateObject3D(entry.stack, toplevel, opt); + + prop.material.wireframe = opt.wireframe; + + prop.material.side = opt.doubleside ? THREE.DoubleSide : THREE.FrontSide; + + var mesh = null; + + if (obj3d.matrixWorld.determinant() > -0.9) { + mesh = new THREE.Mesh(shape.geom, prop.material); + } else { + mesh = createFlippedMesh(obj3d, shape, prop.material); + } + + obj3d.add(mesh); + // specify rendering order, required for transparency handling + //if (obj3d.$jsroot_depth !== undefined) + // mesh.renderOrder = clones.maxdepth - obj3d.$jsroot_depth; + //else + // mesh.renderOrder = clones.maxdepth - entry.stack.length; + } + + //JSROOT.CallBack(call_back, toplevel); + + return toplevel; +} + +function getBoundingBox(node, box3) { + + // extract code of Box3.expandByObject + // Major difference - do not traverse hierarchy + + if (!node || !node.geometry) return box3; + + if (!box3) { + box3 = new THREE.Box3(); + box3.makeEmpty(); + } + + node.updateMatrixWorld(); + + var v1 = new THREE.Vector3(), + geometry = node.geometry; + + if (geometry.isGeometry) { + var vertices = geometry.vertices; + for (var i = 0, l = vertices.length; i < l; i++) { + v1.copy(vertices[i]); + v1.applyMatrix4(node.matrixWorld); + box3.expandByPoint(v1); + } + } else if (geometry.isBufferGeometry) { + var attribute = geometry.attributes.position; + if (attribute !== undefined) { + for (var i = 0, l = attribute.count; i < l; i++) { + // v1.fromAttribute( attribute, i ).applyMatrix4( node.matrixWorld ); + v1.fromBufferAttribute(attribute, i).applyMatrix4(node.matrixWorld); box3.expandByPoint(v1); } - } else if (geometry.isBufferGeometry) { - var attribute = geometry.attributes.position; - if (attribute !== undefined) { - for (var i = 0, l = attribute.count; i < l; i++) { - // v1.fromAttribute( attribute, i ).applyMatrix4( node.matrixWorld ); - v1.fromBufferAttribute(attribute, i).applyMatrix4(node.matrixWorld); - box3.expandByPoint(v1); - } - } } + } - return box3; - }; - - - return JSROOT.GEO; - -})); + return box3; +} diff --git a/dataforge-vis-spatial-js/src/main/resources/JSRootUtils.js b/dataforge-vis-spatial-js/src/main/resources/JSRootUtils.js new file mode 100644 index 00000000..4b881a8c --- /dev/null +++ b/dataforge-vis-spatial-js/src/main/resources/JSRootUtils.js @@ -0,0 +1,761 @@ + +/** Generate mask for given bit + * + * @param {number} n bit number + * @returns {Number} produced make + * @private */ +export function BIT(n) { + return 1 << (n); +} + +/** Wrapper for console.log, let redirect output to specified div element + * @private */ +function console(value, divid) { + if ((typeof divid == 'string') && document.getElementById(divid)) + document.getElementById(divid).innerHTML = value; + else if ((typeof console != 'undefined') && (typeof console.log == 'function')) + console.log(value); +} + + +/** @summary Wrapper for alert, throws Error in Node.js + * @private */ +export function alert(msg) { + if (this.nodeis) throw new Error(msg); + if (typeof alert === 'function') alert(msg); + else console('ALERT: ' + msg); +} + +/** + * @summary Seed simple random generator + * + * @private + * @param {number} i seed value + */ +export function seed(i) { + i = Math.abs(i); + if (i > 1e8) i = Math.abs(1e8 * Math.sin(i)); else if (i < 1) i *= 1e8; + this.m_w = Math.round(i); + this.m_z = 987654321; +} + +/** + * @summary Simple random generator + * + * @desc Works like Math.random(), but with configurable seed - see {@link JSROOT.seed} + * @private + * @returns {number} random value between 0 (inclusive) and 1.0 (exclusive) + */ +export function random() { + if (this.m_z === undefined) return Math.random(); + this.m_z = (36969 * (this.m_z & 65535) + (this.m_z >> 16)) & 0xffffffff; + this.m_w = (18000 * (this.m_w & 65535) + (this.m_w >> 16)) & 0xffffffff; + var result = ((this.m_z << 16) + this.m_w) & 0xffffffff; + result /= 4294967296; + return result + 0.5; +} + + +/** @summary Should be used to reintroduce objects references, produced by TBufferJSON. + * + * @desc Replace all references inside object, object should not be null + * Idea of the code taken from JSON-R code, found on + * https://github.com/graniteds/jsonr + * Only unref part was used, arrays are not accounted as objects + * @param {object} obj object where references will be replaced + * @returns {object} same object with replaced references + * @private */ +function JSONR_unref(obj) { + + var map = [], newfmt = undefined; + + function unref_value(value) { + if ((value === null) || (value === undefined)) return; + + if (typeof value === 'string') { + if (newfmt || (value.length < 6) || (value.indexOf("$ref:") !== 0)) return; + let ref = parseInt(value.substr(5)); + if (isNaN(ref) || (ref < 0) || (ref >= map.length)) return; + newfmt = false; + return map[ref]; + } + + if (typeof value !== 'object') return; + + var i, k, res, proto = Object.prototype.toString.apply(value); + + // scan array - it can contain other objects + if ((proto.indexOf('[object') === 0) && (proto.indexOf('Array]') > 0)) { + for (i = 0; i < value.length; ++i) { + res = unref_value(value[i]); + if (res !== undefined) value[i] = res; + } + return; + } + + var ks = Object.keys(value), len = ks.length; + + if ((newfmt !== false) && (len === 1) && (ks[0] === '$ref')) { + var ref = parseInt(value['$ref']); + if (isNaN(ref) || (ref < 0) || (ref >= map.length)) return; + newfmt = true; + return map[ref]; + } + + if ((newfmt !== false) && (len > 1) && (ks[0] === '$arr') && (ks[1] === 'len')) { + // this is ROOT-coded array + var arr = null, dflt = (value.$arr === "Bool") ? false : 0; + switch (value.$arr) { + case "Int8" : + arr = new Int8Array(value.len); + break; + case "Uint8" : + arr = new Uint8Array(value.len); + break; + case "Int16" : + arr = new Int16Array(value.len); + break; + case "Uint16" : + arr = new Uint16Array(value.len); + break; + case "Int32" : + arr = new Int32Array(value.len); + break; + case "Uint32" : + arr = new Uint32Array(value.len); + break; + case "Float32" : + arr = new Float32Array(value.len); + break; + case "Int64" : + case "Uint64" : + case "Float64" : + arr = new Float64Array(value.len); + break; + default : + arr = new Array(value.len); + break; + } + for (var k = 0; k < value.len; ++k) arr[k] = dflt; + + var nkey = 2, p = 0; + while (nkey < len) { + if (ks[nkey][0] === "p") p = value[ks[nkey++]]; // position + if (ks[nkey][0] !== 'v') throw new Error('Unexpected member ' + ks[nkey] + ' in array decoding'); + var v = value[ks[nkey++]]; // value + if (typeof v === 'object') { + for (var k = 0; k < v.length; ++k) arr[p++] = v[k]; + } else { + arr[p++] = v; + if ((nkey < len) && (ks[nkey][0] === 'n')) { + var cnt = value[ks[nkey++]]; // counter + while (--cnt) arr[p++] = v; + } + } + } + + return arr; + } + + if ((newfmt !== false) && (len === 3) && (ks[0] === '$pair') && (ks[1] === 'first') && (ks[2] === 'second')) { + newfmt = true; + var f1 = unref_value(value.first), + s1 = unref_value(value.second); + if (f1 !== undefined) value.first = f1; + if (s1 !== undefined) value.second = s1; + value._typename = value['$pair']; + delete value['$pair']; + return; // pair object is not counted in the objects map + } + + // debug code, can be commented out later + if (map.indexOf(value) >= 0) { + console('should never happen - object already in the map'); + return; + } + + // add object to object map + map.push(value); + + // add methods to all objects, where _typename is specified + //if ('_typename' in value) JSROOT.addMethods(value); + + for (k = 0; k < len; ++k) { + i = ks[k]; + res = unref_value(value[i]); + if (res !== undefined) value[i] = res; + } + } + + unref_value(obj); + + return obj; +} + + +/** @summary Just copies (not clone) all fields from source to the target object + * @desc This is simple replacement of jQuery.extend method + * @private */ +function extend(tgt, src) { + if ((src === null) || (typeof src !== 'object')) return tgt; + if ((tgt === null) || (typeof tgt !== 'object')) tgt = {}; + + for (var k in src) + tgt[k] = src[k]; + + return tgt; +} + + +/** + * @summary Parse JSON code produced with TBufferJSON. + * + * @param {string} json string to parse + * @return {object|null} returns parsed object + */ +export function parse(json) { + if (!json) return null; + let obj = JSON.parse(json); + if (obj) obj = JSONR_unref(obj); + return obj; +} + + +/** + * @summary Parse multi.json request results + * @desc Method should be used to parse JSON code, produced by multi.json request of THttpServer + * + * @param {string} json string to parse + * @return {Array|null} returns array of parsed elements + */ +export function parse_multi(json) { + if (!json) return null; + let arr = JSON.parse(json); + if (arr && arr.length) + for (let i = 0; i < arr.length; ++i) + arr[i] = JSONR_unref(arr[i]); + return arr; +} + +/** + * @summary Method converts JavaScript object into ROOT-like JSON + * + * @desc Produced JSON can be used in JSROOT.parse() again + * When performed properly, JSON can be used in TBufferJSON to read data back with C++ + */ +export function toJSON(obj) { + if (!obj || typeof obj !== 'object') return ""; + + var map = []; // map of stored objects + + function copy_value(value) { + if (typeof value === "function") return undefined; + + if ((value === undefined) || (value === null) || (typeof value !== 'object')) return value; + + var proto = Object.prototype.toString.apply(value); + + // typed array need to be converted into normal array, otherwise looks strange + if ((proto.indexOf('[object ') === 0) && (proto.indexOf('Array]') === proto.length - 6)) { + var arr = new Array(value.length); + for (var i = 0; i < value.length; ++i) + arr[i] = copy_value(value[i]); + return arr; + } + + // this is how reference is code + var refid = map.indexOf(value); + if (refid >= 0) return {$ref: refid}; + + var ks = Object.keys(value), len = ks.length, tgt = {}; + + if ((len === 3) && (ks[0] === '$pair') && (ks[1] === 'first') && (ks[2] === 'second')) { + // special handling of pair objects which does not included into objects map + tgt.$pair = value.$pair; + tgt.first = copy_value(value.first); + tgt.second = copy_value(value.second); + return tgt; + } + + map.push(value); + + for (var k = 0; k < len; ++k) { + var name = ks[k]; + tgt[name] = copy_value(value[name]); + } + + return tgt; + } + + var tgt = copy_value(obj); + + return JSON.stringify(tgt); +} + + +/** + * @summary Parse string value as array. + * + * @desc It could be just simple string: "value" or + * array with or without string quotes: [element], ['elem1',elem2] + * + * @private + */ +function ParseAsArray(val) { + + var res = []; + + if (typeof val != 'string') return res; + + val = val.trim(); + if (val === "") return res; + + // return as array with single element + if ((val.length < 2) || (val[0] !== '[') || (val[val.length - 1] !== ']')) { + res.push(val); + return res; + } + + // try to split ourself, checking quotes and brackets + var nbr = 0, nquotes = 0, ndouble = 0, last = 1; + + for (var indx = 1; indx < val.length; ++indx) { + if (nquotes > 0) { + if (val[indx] === "'") nquotes--; + continue; + } + if (ndouble > 0) { + if (val[indx] === '"') ndouble--; + continue; + } + switch (val[indx]) { + case "'": + nquotes++; + break; + case '"': + ndouble++; + break; + case "[": + nbr++; + break; + case "]": + if (indx < val.length - 1) { + nbr--; + break; + } + case ",": + if (nbr === 0) { + var sub = val.substring(last, indx).trim(); + if ((sub.length > 1) && (sub[0] === sub[sub.length - 1]) && ((sub[0] === '"') || (sub[0] === "'"))) + sub = sub.substr(1, sub.length - 2); + res.push(sub); + last = indx + 1; + } + break; + } + } + + if (res.length === 0) + res.push(val.substr(1, val.length - 2).trim()); + + return res; +} + +/** + * @summary Find function with given name. + * + * @desc Function name may include several namespaces like 'JSROOT.Painter.drawFrame' + * + * @private + */ +function findFunction(name) { + if (typeof name === 'function') return name; + if (typeof name !== 'string') return null; + var names = name.split('.'), elem = null; + if (typeof window === 'object') elem = window; + if (names[0] === 'JSROOT') { + elem = this; + names.shift(); + } + + for (var n = 0; elem && (n < names.length); ++n) + elem = elem[names[n]]; + + return (typeof elem == 'function') ? elem : null; +} + +/** + * @summary Generic method to invoke callback function. + * + * @param {object|function} func either normal function or container like + * { obj: object_pointer, func: name of method to call } + * @param arg1 first optional argument of callback + * @param arg2 second optional argument of callback + * + * @private + */ +function callBack(func, arg1, arg2) { + + if (typeof func == 'string') func = findFunction(func); + + if (!func) return; + + if (typeof func == 'function') return func(arg1, arg2); + + if (typeof func != 'object') return; + + if (('obj' in func) && ('func' in func) && + (typeof func.obj == 'object') && (typeof func.func == 'string') && + (typeof func.obj[func.func] == 'function')) { + return func.obj[func.func](arg1, arg2); + } +} + +let methodsCache = {}; // variable used to keep methods for known classes + + +/** @summary Returns methods for given typename + * @private + */ +function getMethods(typename, obj) { + + var m = methodsCache[typename], + has_methods = (m !== undefined); + + if (!has_methods) m = {}; + + // Due to binary I/O such TObject methods may not be set for derived classes + // Therefore when methods requested for given object, check also that basic methods are there + if ((typename === "TObject") || (typename === "TNamed") || (obj && (obj.fBits !== undefined))) + if (m.TestBit === undefined) { + m.TestBit = function (f) { + return (this.fBits & f) !== 0; + }; + m.InvertBit = function (f) { + this.fBits = this.fBits ^ (f & 0xffffff); + }; + } + + if (has_methods) return m; + + if ((typename === 'TList') || (typename === 'THashList')) { + m.Clear = function () { + this.arr = []; + this.opt = []; + }; + m.Add = function (obj, opt) { + this.arr.push(obj); + this.opt.push((opt && typeof opt == 'string') ? opt : ""); + }; + m.AddFirst = function (obj, opt) { + this.arr.unshift(obj); + this.opt.unshift((opt && typeof opt == 'string') ? opt : ""); + }; + m.RemoveAt = function (indx) { + this.arr.splice(indx, 1); + this.opt.splice(indx, 1); + } + } + + // if ((typename === "TPaveText") || (typename === "TPaveStats")) { + // m.AddText = function (txt) { + // // this.fLines.Add({ _typename: 'TLatex', fTitle: txt, fTextColor: 1 }); + // var line = JSROOT.Create("TLatex"); + // line.fTitle = txt; + // this.fLines.Add(line); + // }; + // m.Clear = function () { + // this.fLines.Clear(); + // } + // } + // + // if ((typename.indexOf("TF1") === 0) || (typename === "TF2")) { + // m.addFormula = function (obj) { + // if (!obj) return; + // if (this.formulas === undefined) this.formulas = []; + // this.formulas.push(obj); + // }; + // + // m.evalPar = function (x, y) { + // if (!('_func' in this) || (this._title !== this.fTitle)) { + // + // var _func = this.fTitle, isformula = false, pprefix = "["; + // if (_func === "gaus") _func = "gaus(0)"; + // if (this.fFormula && typeof this.fFormula.fFormula == "string") { + // if (this.fFormula.fFormula.indexOf("[](double*x,double*p)") === 0) { + // isformula = true; + // pprefix = "p["; + // _func = this.fFormula.fFormula.substr(21); + // } else { + // _func = this.fFormula.fFormula; + // pprefix = "[p"; + // } + // if (this.fFormula.fClingParameters && this.fFormula.fParams) { + // for (var i = 0; i < this.fFormula.fParams.length; ++i) { + // var regex = new RegExp('(\\[' + this.fFormula.fParams[i].first + '\\])', 'g'), + // parvalue = this.fFormula.fClingParameters[this.fFormula.fParams[i].second]; + // _func = _func.replace(regex, (parvalue < 0) ? "(" + parvalue + ")" : parvalue); + // } + // } + // } + // + // if ('formulas' in this) + // for (var i = 0; i < this.formulas.length; ++i) + // while (_func.indexOf(this.formulas[i].fName) >= 0) + // _func = _func.replace(this.formulas[i].fName, this.formulas[i].fTitle); + // _func = _func.replace(/\b(abs)\b/g, 'TMath::Abs') + // .replace(/TMath::Exp\(/g, 'Math.exp(') + // .replace(/TMath::Abs\(/g, 'Math.abs('); + // if (typeof JSROOT.Math == 'object') { + // this._math = JSROOT.Math; + // _func = _func.replace(/TMath::Prob\(/g, 'this._math.Prob(') + // .replace(/TMath::Gaus\(/g, 'this._math.Gaus(') + // .replace(/TMath::BreitWigner\(/g, 'this._math.BreitWigner(') + // .replace(/xygaus\(/g, 'this._math.gausxy(this, x, y, ') + // .replace(/gaus\(/g, 'this._math.gaus(this, x, ') + // .replace(/gausn\(/g, 'this._math.gausn(this, x, ') + // .replace(/expo\(/g, 'this._math.expo(this, x, ') + // .replace(/landau\(/g, 'this._math.landau(this, x, ') + // .replace(/landaun\(/g, 'this._math.landaun(this, x, ') + // .replace(/ROOT::Math::/g, 'this._math.'); + // } + // for (var i = 0; i < this.fNpar; ++i) { + // var parname = pprefix + i + "]"; + // while (_func.indexOf(parname) !== -1) + // _func = _func.replace(parname, '(' + this.GetParValue(i) + ')'); + // } + // _func = _func.replace(/\b(sin)\b/gi, 'Math.sin') + // .replace(/\b(cos)\b/gi, 'Math.cos') + // .replace(/\b(tan)\b/gi, 'Math.tan') + // .replace(/\b(exp)\b/gi, 'Math.exp') + // .replace(/\b(pow)\b/gi, 'Math.pow') + // .replace(/pi/g, 'Math.PI'); + // for (var n = 2; n < 10; ++n) + // _func = _func.replace('x^' + n, 'Math.pow(x,' + n + ')'); + // + // if (isformula) { + // _func = _func.replace(/x\[0\]/g, "x"); + // if (this._typename === "TF2") { + // _func = _func.replace(/x\[1\]/g, "y"); + // this._func = new Function("x", "y", _func).bind(this); + // } else { + // this._func = new Function("x", _func).bind(this); + // } + // } else if (this._typename === "TF2") + // this._func = new Function("x", "y", "return " + _func).bind(this); + // else + // this._func = new Function("x", "return " + _func).bind(this); + // + // this._title = this.fTitle; + // } + // + // return this._func(x, y); + // }; + // m.GetParName = function (n) { + // if (this.fFormula && this.fFormula.fParams) return this.fFormula.fParams[n].first; + // if (this.fNames && this.fNames[n]) return this.fNames[n]; + // return "p" + n; + // }; + // m.GetParValue = function (n) { + // if (this.fFormula && this.fFormula.fClingParameters) return this.fFormula.fClingParameters[n]; + // if (this.fParams) return this.fParams[n]; + // return undefined; + // }; + // m.GetParError = function (n) { + // return this.fParErrors ? this.fParErrors[n] : undefined; + // }; + // m.GetNumPars = function () { + // return this.fNpar; + // } + // } + + if (((typename.indexOf("TGraph") === 0) || (typename === "TCutG")) && (typename !== "TGraphPolargram") && (typename !== "TGraphTime")) { + // check if point inside figure specified by the TGraph + m.IsInside = function (xp, yp) { + var i, j = this.fNpoints - 1, x = this.fX, y = this.fY, oddNodes = false; + + for (i = 0; i < this.fNpoints; ++i) { + if ((y[i] < yp && y[j] >= yp) || (y[j] < yp && y[i] >= yp)) { + if (x[i] + (yp - y[i]) / (y[j] - y[i]) * (x[j] - x[i]) < xp) { + oddNodes = !oddNodes; + } + } + j = i; + } + + return oddNodes; + }; + } + + if (typename.indexOf("TH1") === 0 || + typename.indexOf("TH2") === 0 || + typename.indexOf("TH3") === 0) { + m.getBinError = function (bin) { + // -*-*-*-*-*Return value of error associated to bin number bin*-*-*-*-* + // if the sum of squares of weights has been defined (via Sumw2), + // this function returns the sqrt(sum of w2). + // otherwise it returns the sqrt(contents) for this bin. + if (bin >= this.fNcells) bin = this.fNcells - 1; + if (bin < 0) bin = 0; + if (bin < this.fSumw2.length) + return Math.sqrt(this.fSumw2[bin]); + return Math.sqrt(Math.abs(this.fArray[bin])); + }; + m.setBinContent = function (bin, content) { + // Set bin content - only trivial case, without expansion + this.fEntries++; + this.fTsumw = 0; + if ((bin >= 0) && (bin < this.fArray.length)) + this.fArray[bin] = content; + }; + } + + if (typename.indexOf("TH1") === 0) { + m.getBin = function (x) { + return x; + }; + m.getBinContent = function (bin) { + return this.fArray[bin]; + }; + m.Fill = function (x, weight) { + var axis = this.fXaxis, + bin = 1 + Math.floor((x - axis.fXmin) / (axis.fXmax - axis.fXmin) * axis.fNbins); + if (bin < 0) bin = 0; else if (bin > axis.fNbins + 1) bin = axis.fNbins + 1; + this.fArray[bin] += ((weight === undefined) ? 1 : weight); + } + } + + if (typename.indexOf("TH2") === 0) { + m.getBin = function (x, y) { + return (x + (this.fXaxis.fNbins + 2) * y); + }; + m.getBinContent = function (x, y) { + return this.fArray[this.getBin(x, y)]; + }; + m.Fill = function (x, y, weight) { + var axis1 = this.fXaxis, axis2 = this.fYaxis, + bin1 = 1 + Math.floor((x - axis1.fXmin) / (axis1.fXmax - axis1.fXmin) * axis1.fNbins), + bin2 = 1 + Math.floor((y - axis2.fXmin) / (axis2.fXmax - axis2.fXmin) * axis2.fNbins); + if (bin1 < 0) bin1 = 0; else if (bin1 > axis1.fNbins + 1) bin1 = axis1.fNbins + 1; + if (bin2 < 0) bin2 = 0; else if (bin2 > axis2.fNbins + 1) bin2 = axis2.fNbins + 1; + this.fArray[bin1 + (axis1.fNbins + 2) * bin2] += ((weight === undefined) ? 1 : weight); + } + } + + if (typename.indexOf("TH3") === 0) { + m.getBin = function (x, y, z) { + return (x + (this.fXaxis.fNbins + 2) * (y + (this.fYaxis.fNbins + 2) * z)); + }; + m.getBinContent = function (x, y, z) { + return this.fArray[this.getBin(x, y, z)]; + }; + m.Fill = function (x, y, z, weight) { + var axis1 = this.fXaxis, axis2 = this.fYaxis, axis3 = this.fZaxis, + bin1 = 1 + Math.floor((x - axis1.fXmin) / (axis1.fXmax - axis1.fXmin) * axis1.fNbins), + bin2 = 1 + Math.floor((y - axis2.fXmin) / (axis2.fXmax - axis2.fXmin) * axis2.fNbins), + bin3 = 1 + Math.floor((z - axis3.fXmin) / (axis3.fXmax - axis3.fXmin) * axis3.fNbins); + if (bin1 < 0) bin1 = 0; else if (bin1 > axis1.fNbins + 1) bin1 = axis1.fNbins + 1; + if (bin2 < 0) bin2 = 0; else if (bin2 > axis2.fNbins + 1) bin2 = axis2.fNbins + 1; + if (bin3 < 0) bin3 = 0; else if (bin3 > axis3.fNbins + 1) bin3 = axis3.fNbins + 1; + this.fArray[bin1 + (axis1.fNbins + 2) * (bin2 + (axis2.fNbins + 2) * bin3)] += ((weight === undefined) ? 1 : weight); + } + } + + if (typename.indexOf("TProfile") === 0) { + if (typename.indexOf("TProfile2D") === 0) { + m.getBin = function (x, y) { + return (x + (this.fXaxis.fNbins + 2) * y); + }; + m.getBinContent = function (x, y) { + var bin = this.getBin(x, y); + if (bin < 0 || bin >= this.fNcells) return 0; + if (this.fBinEntries[bin] < 1e-300) return 0; + if (!this.fArray) return 0; + return this.fArray[bin] / this.fBinEntries[bin]; + }; + m.getBinEntries = function (x, y) { + var bin = this.getBin(x, y); + if (bin < 0 || bin >= this.fNcells) return 0; + return this.fBinEntries[bin]; + } + } else { + m.getBin = function (x) { + return x; + }; + m.getBinContent = function (bin) { + if (bin < 0 || bin >= this.fNcells) return 0; + if (this.fBinEntries[bin] < 1e-300) return 0; + if (!this.fArray) return 0; + return this.fArray[bin] / this.fBinEntries[bin]; + }; + } + m.getBinEffectiveEntries = function (bin) { + if (bin < 0 || bin >= this.fNcells) return 0; + var sumOfWeights = this.fBinEntries[bin]; + if (!this.fBinSumw2 || this.fBinSumw2.length !== this.fNcells) { + // this can happen when reading an old file + return sumOfWeights; + } + var sumOfWeightsSquare = this.fBinSumw2[bin]; + return (sumOfWeightsSquare > 0) ? sumOfWeights * sumOfWeights / sumOfWeightsSquare : 0; + }; + m.getBinError = function (bin) { + if (bin < 0 || bin >= this.fNcells) return 0; + var cont = this.fArray[bin], // sum of bin w *y + sum = this.fBinEntries[bin], // sum of bin weights + err2 = this.fSumw2[bin], // sum of bin w * y^2 + neff = this.getBinEffectiveEntries(bin); // (sum of w)^2 / (sum of w^2) + if (sum < 1e-300) return 0; // for empty bins + var EErrorType = {kERRORMEAN: 0, kERRORSPREAD: 1, kERRORSPREADI: 2, kERRORSPREADG: 3}; + // case the values y are gaussian distributed y +/- sigma and w = 1/sigma^2 + if (this.fErrorMode === EErrorType.kERRORSPREADG) + return 1.0 / Math.sqrt(sum); + // compute variance in y (eprim2) and standard deviation in y (eprim) + var contsum = cont / sum, eprim = Math.sqrt(Math.abs(err2 / sum - contsum * contsum)); + if (this.fErrorMode === EErrorType.kERRORSPREADI) { + if (eprim !== 0) return eprim / Math.sqrt(neff); + // in case content y is an integer (so each my has an error +/- 1/sqrt(12) + // when the std(y) is zero + return 1.0 / Math.sqrt(12 * neff); + } + // if approximate compute the sums (of w, wy and wy2) using all the bins + // when the variance in y is zero + // case option "S" return standard deviation in y + if (this.fErrorMode === EErrorType.kERRORSPREAD) return eprim; + // default case : fErrorMode = kERRORMEAN + // return standard error on the mean of y + return (eprim / Math.sqrt(neff)); + }; + } + + if (typename === "TAxis") { + m.GetBinLowEdge = function (bin) { + if (this.fNbins <= 0) return 0; + if ((this.fXbins.length > 0) && (bin > 0) && (bin <= this.fNbins)) return this.fXbins[bin - 1]; + return this.fXmin + (bin - 1) * (this.fXmax - this.fXmin) / this.fNbins; + }; + m.GetBinCenter = function (bin) { + if (this.fNbins <= 0) return 0; + if ((this.fXbins.length > 0) && (bin > 0) && (bin < this.fNbins)) return (this.fXbins[bin - 1] + this.fXbins[bin]) / 2; + return this.fXmin + (bin - 0.5) * (this.fXmax - this.fXmin) / this.fNbins; + } + } + + if (typeof getMoreMethods == "function") + getMoreMethods(m, typename, obj); + + methodsCache[typename] = m; + return m; +} + + +/** @summary Adds specific methods to the object. + * + * JSROOT implements some basic methods for different ROOT classes. + * @param {object} obj - object where methods are assigned + * @param {string} typename - optional typename, if not specified, obj._typename will be used + * @private + */ +function addMethods(obj, typename) { + this.extend(obj, getMethods(typename || obj._typename, obj)); +} diff --git a/dataforge-vis-spatial-js/src/main/resources/ThreeCSG.js b/dataforge-vis-spatial-js/src/main/resources/ThreeCSG.js index 541c6429..9ec0707a 100644 --- a/dataforge-vis-spatial-js/src/main/resources/ThreeCSG.js +++ b/dataforge-vis-spatial-js/src/main/resources/ThreeCSG.js @@ -1,504 +1,464 @@ -(function (factory) { - if (typeof define === "function" && define.amd) { - define(['three-full'], factory); - } else if (typeof exports === 'object' && typeof module !== 'undefined') { - factory(require("three-full"), exports); - } else { +import * as THREE from "three-full" - if (typeof THREE == 'undefined') - throw new Error('THREE is not defined', 'ThreeCSG.js'); +const EPSILON = 1e-5, + COPLANAR = 0, + FRONT = 1, + BACK = 2, + SPANNING = 3; - ThreeBSP = factory(THREE); - } -}(function (THREE, ThreeBSP) { +export function Geometry(geometry, transfer_matrix, nodeid, flippedMesh) { + // Convert THREE.Geometry to ThreeBSP - "use strict"; + if (geometry instanceof THREE.Geometry) { + this.matrix = null; // new THREE.Matrix4; not create matrix when do not needed + } else if (geometry instanceof THREE.Mesh) { + // #todo: add hierarchy support + geometry.updateMatrix(); + transfer_matrix = this.matrix = geometry.matrix.clone(); + geometry = geometry.geometry; + } else if (geometry instanceof Node) { + this.tree = geometry; + this.matrix = null; // new THREE.Matrix4; + return this; + } else if (geometry instanceof THREE.BufferGeometry) { + var pos_buf = geometry.getAttribute('position').array, + norm_buf = geometry.getAttribute('normal').array, + polygons = [], polygon, vert1, vert2, vert3; - if (!ThreeBSP) ThreeBSP = {}; + for (var i = 0; i < pos_buf.length; i += 9) { + polygon = new Polygon; - var EPSILON = 1e-5, - COPLANAR = 0, - FRONT = 1, - BACK = 2, - SPANNING = 3; + vert1 = new Vertex(pos_buf[i], pos_buf[i + 1], pos_buf[i + 2], norm_buf[i], norm_buf[i + 1], norm_buf[i + 2]); + if (transfer_matrix) vert1.applyMatrix4(transfer_matrix); - ThreeBSP.Geometry = function (geometry, transfer_matrix, nodeid, flippedMesh) { - // Convert THREE.Geometry to ThreeBSP + vert2 = new Vertex(pos_buf[i + 3], pos_buf[i + 4], pos_buf[i + 5], norm_buf[i + 3], norm_buf[i + 4], norm_buf[i + 5]); + if (transfer_matrix) vert2.applyMatrix4(transfer_matrix); - if (geometry instanceof THREE.Geometry) { - this.matrix = null; // new THREE.Matrix4; not create matrix when do not needed - } else if (geometry instanceof THREE.Mesh) { - // #todo: add hierarchy support - geometry.updateMatrix(); - transfer_matrix = this.matrix = geometry.matrix.clone(); - geometry = geometry.geometry; - } else if (geometry instanceof ThreeBSP.Node) { - this.tree = geometry; - this.matrix = null; // new THREE.Matrix4; - return this; - } else if (geometry instanceof THREE.BufferGeometry) { - var pos_buf = geometry.getAttribute('position').array, - norm_buf = geometry.getAttribute('normal').array, - polygons = [], polygon, vert1, vert2, vert3; + vert3 = new Vertex(pos_buf[i + 6], pos_buf[i + 7], pos_buf[i + 8], norm_buf[i + 6], norm_buf[i + 7], norm_buf[i + 8]); + if (transfer_matrix) vert3.applyMatrix4(transfer_matrix); - for (var i = 0; i < pos_buf.length; i += 9) { - polygon = new ThreeBSP.Polygon; - - vert1 = new ThreeBSP.Vertex(pos_buf[i], pos_buf[i + 1], pos_buf[i + 2], norm_buf[i], norm_buf[i + 1], norm_buf[i + 2]); - if (transfer_matrix) vert1.applyMatrix4(transfer_matrix); - - vert2 = new ThreeBSP.Vertex(pos_buf[i + 3], pos_buf[i + 4], pos_buf[i + 5], norm_buf[i + 3], norm_buf[i + 4], norm_buf[i + 5]); - if (transfer_matrix) vert2.applyMatrix4(transfer_matrix); - - vert3 = new ThreeBSP.Vertex(pos_buf[i + 6], pos_buf[i + 7], pos_buf[i + 8], norm_buf[i + 6], norm_buf[i + 7], norm_buf[i + 8]); - if (transfer_matrix) vert3.applyMatrix4(transfer_matrix); - - if (flippedMesh) polygon.vertices.push(vert1, vert3, vert2); - else polygon.vertices.push(vert1, vert2, vert3); - - polygon.calculateProperties(); - polygons.push(polygon); - } - - this.tree = new ThreeBSP.Node(polygons, nodeid); - if (nodeid !== undefined) this.maxid = this.tree.maxnodeid; - return this; - - } else if (geometry.polygons && (geometry.polygons[0] instanceof ThreeBSP.Polygon)) { - var polygons = geometry.polygons; - - for (var i = 0; i < polygons.length; ++i) { - var polygon = polygons[i]; - if (transfer_matrix) { - for (var n = 0; n < polygon.vertices.length; ++n) - polygon.vertices[n].applyMatrix4(transfer_matrix); - } - - polygon.calculateProperties(); - } - - this.tree = new ThreeBSP.Node(polygons, nodeid); - if (nodeid !== undefined) this.maxid = this.tree.maxnodeid; - return this; - - } else { - throw 'ThreeBSP: Given geometry is unsupported'; - } - - var polygons = [], - nfaces = geometry.faces.length, - face, polygon, vertex; - - for (var i = 0; i < nfaces; ++i) { - face = geometry.faces[i]; - // faceVertexUvs = geometry.faceVertexUvs[0][i]; - polygon = new ThreeBSP.Polygon; - - if (face instanceof THREE.Face3) { - vertex = geometry.vertices[face.a]; - // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[0].x, faceVertexUvs[0].y ) : null; - vertex = new ThreeBSP.Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[0].x, face.vertexNormals[0].y, face.vertexNormals[0].z /*face.normal , uvs */); - if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); - polygon.vertices.push(vertex); - - vertex = geometry.vertices[face.b]; - //uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[1].x, faceVertexUvs[1].y ) : null; - vertex = new ThreeBSP.Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[1].x, face.vertexNormals[1].y, face.vertexNormals[1].z/*face.normal , uvs */); - if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); - polygon.vertices.push(vertex); - - vertex = geometry.vertices[face.c]; - // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[2].x, faceVertexUvs[2].y ) : null; - vertex = new ThreeBSP.Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[2].x, face.vertexNormals[2].y, face.vertexNormals[2].z /*face.normal, uvs */); - if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); - polygon.vertices.push(vertex); - } else if (typeof THREE.Face4) { - vertex = geometry.vertices[face.a]; - // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[0].x, faceVertexUvs[0].y ) : null; - vertex = new ThreeBSP.Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[0].x, face.vertexNormals[0].y, face.vertexNormals[0].z /*, uvs */); - if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); - polygon.vertices.push(vertex); - - vertex = geometry.vertices[face.b]; - // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[1].x, faceVertexUvs[1].y ) : null; - vertex = new ThreeBSP.Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[1].x, face.vertexNormals[1].y, face.vertexNormals[1].z /*, uvs */); - if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); - polygon.vertices.push(vertex); - - vertex = geometry.vertices[face.c]; - // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[2].x, faceVertexUvs[2].y ) : null; - vertex = new ThreeBSP.Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[2].x, face.vertexNormals[2].y, face.vertexNormals[2].z /*, uvs */); - if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); - polygon.vertices.push(vertex); - - vertex = geometry.vertices[face.d]; - // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[3].x, faceVertexUvs[3].y ) : null; - vertex = new ThreeBSP.Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[3].x, face.vertexNormals[3].y, face.vertexNormals[3].z /*, uvs */); - if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); - polygon.vertices.push(vertex); - } else { - throw 'Invalid face type at index ' + i; - } + if (flippedMesh) polygon.vertices.push(vert1, vert3, vert2); + else polygon.vertices.push(vert1, vert2, vert3); polygon.calculateProperties(); polygons.push(polygon); } - this.tree = new ThreeBSP.Node(polygons, nodeid); + this.tree = new Node(polygons, nodeid); if (nodeid !== undefined) this.maxid = this.tree.maxnodeid; - }; + return this; - ThreeBSP.Geometry.prototype.subtract = function (other_tree) { - var a = this.tree.clone(), - b = other_tree.tree.clone(); + } else if (geometry.polygons && (geometry.polygons[0] instanceof Polygon)) { + var polygons = geometry.polygons; - a.invert(); - a.clipTo(b); - b.clipTo(a); - b.invert(); - b.clipTo(a); - b.invert(); - a.build(b.allPolygons()); - a.invert(); - a = new ThreeBSP.Geometry(a); - a.matrix = this.matrix; - return a; - }; + for (var i = 0; i < polygons.length; ++i) { + var polygon = polygons[i]; + if (transfer_matrix) { + for (var n = 0; n < polygon.vertices.length; ++n) + polygon.vertices[n].applyMatrix4(transfer_matrix); + } - ThreeBSP.Geometry.prototype.union = function (other_tree) { - var a = this.tree.clone(), - b = other_tree.tree.clone(); - - a.clipTo(b); - b.clipTo(a); - b.invert(); - b.clipTo(a); - b.invert(); - a.build(b.allPolygons()); - a = new ThreeBSP.Geometry(a); - a.matrix = this.matrix; - return a; - }; - - ThreeBSP.Geometry.prototype.intersect = function (other_tree) { - var a = this.tree.clone(), - b = other_tree.tree.clone(); - - a.invert(); - b.clipTo(a); - b.invert(); - a.clipTo(b); - b.clipTo(a); - a.build(b.allPolygons()); - a.invert(); - a = new ThreeBSP.Geometry(a); - a.matrix = this.matrix; - return a; - }; - - ThreeBSP.Geometry.prototype.tryToCompress = function (polygons) { - - if (this.maxid === undefined) return; - - var arr = [], parts, foundpair, - nreduce = 0, n, len = polygons.length, - p, p1, p2, i1, i2; - - // sort out polygons - for (n = 0; n < len; ++n) { - p = polygons[n]; - if (p.id === undefined) continue; - if (arr[p.id] === undefined) arr[p.id] = []; - - arr[p.id].push(p); + polygon.calculateProperties(); } - for (n = 0; n < arr.length; ++n) { - parts = arr[n]; - if (parts === undefined) continue; + this.tree = new Node(polygons, nodeid); + if (nodeid !== undefined) this.maxid = this.tree.maxnodeid; + return this; - len = parts.length; + } else { + throw 'ThreeBSP: Given geometry is unsupported'; + } - foundpair = (len > 1); + var polygons = [], + nfaces = geometry.faces.length, + face, polygon, vertex; - while (foundpair) { - foundpair = false; + for (var i = 0; i < nfaces; ++i) { + face = geometry.faces[i]; + // faceVertexUvs = geometry.faceVertexUvs[0][i]; + polygon = new Polygon; - for (i1 = 0; i1 < len - 1; ++i1) { - p1 = parts[i1]; - if (!p1 || !p1.parent) continue; - for (i2 = i1 + 1; i2 < len; ++i2) { - p2 = parts[i2]; - if (p2 && (p1.parent === p2.parent) && (p1.nsign === p2.nsign)) { + if (face instanceof THREE.Face3) { + vertex = geometry.vertices[face.a]; + // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[0].x, faceVertexUvs[0].y ) : null; + vertex = new Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[0].x, face.vertexNormals[0].y, face.vertexNormals[0].z /*face.normal , uvs */); + if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); + polygon.vertices.push(vertex); - if (p1.nsign !== p1.parent.nsign) p1.parent.flip(); + vertex = geometry.vertices[face.b]; + //uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[1].x, faceVertexUvs[1].y ) : null; + vertex = new Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[1].x, face.vertexNormals[1].y, face.vertexNormals[1].z/*face.normal , uvs */); + if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); + polygon.vertices.push(vertex); - nreduce++; - parts[i1] = p1.parent; - parts[i2] = null; - if (p1.parent.vertices.length < 3) console.log('something wrong with parent'); - foundpair = true; - break; - } + vertex = geometry.vertices[face.c]; + // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[2].x, faceVertexUvs[2].y ) : null; + vertex = new Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[2].x, face.vertexNormals[2].y, face.vertexNormals[2].z /*face.normal, uvs */); + if (transfer_matrix) vertex.applyMatrix4(transfer_matrix); + polygon.vertices.push(vertex); + } else { + throw 'Invalid face type at index ' + i; + } + + polygon.calculateProperties(); + polygons.push(polygon); + } + + this.tree = new Node(polygons, nodeid); + if (nodeid !== undefined) this.maxid = this.tree.maxnodeid; +} + +Geometry.prototype.subtract = function (other_tree) { + var a = this.tree.clone(), + b = other_tree.tree.clone(); + + a.invert(); + a.clipTo(b); + b.clipTo(a); + b.invert(); + b.clipTo(a); + b.invert(); + a.build(b.allPolygons()); + a.invert(); + a = new Geometry(a); + a.matrix = this.matrix; + return a; +}; + +Geometry.prototype.union = function (other_tree) { + var a = this.tree.clone(), + b = other_tree.tree.clone(); + + a.clipTo(b); + b.clipTo(a); + b.invert(); + b.clipTo(a); + b.invert(); + a.build(b.allPolygons()); + a = new Geometry(a); + a.matrix = this.matrix; + return a; +}; + +Geometry.prototype.intersect = function (other_tree) { + var a = this.tree.clone(), + b = other_tree.tree.clone(); + + a.invert(); + b.clipTo(a); + b.invert(); + a.clipTo(b); + b.clipTo(a); + a.build(b.allPolygons()); + a.invert(); + a = new Geometry(a); + a.matrix = this.matrix; + return a; +}; + +Geometry.prototype.tryToCompress = function (polygons) { + + if (this.maxid === undefined) return; + + var arr = [], parts, foundpair, + nreduce = 0, n, len = polygons.length, + p, p1, p2, i1, i2; + + // sort out polygons + for (n = 0; n < len; ++n) { + p = polygons[n]; + if (p.id === undefined) continue; + if (arr[p.id] === undefined) arr[p.id] = []; + + arr[p.id].push(p); + } + + for (n = 0; n < arr.length; ++n) { + parts = arr[n]; + if (parts === undefined) continue; + + len = parts.length; + + foundpair = (len > 1); + + while (foundpair) { + foundpair = false; + + for (i1 = 0; i1 < len - 1; ++i1) { + p1 = parts[i1]; + if (!p1 || !p1.parent) continue; + for (i2 = i1 + 1; i2 < len; ++i2) { + p2 = parts[i2]; + if (p2 && (p1.parent === p2.parent) && (p1.nsign === p2.nsign)) { + + if (p1.nsign !== p1.parent.nsign) p1.parent.flip(); + + nreduce++; + parts[i1] = p1.parent; + parts[i2] = null; + if (p1.parent.vertices.length < 3) console.log('something wrong with parent'); + foundpair = true; + break; } } } } + } - if (nreduce > 0) { - polygons.splice(0, polygons.length); + if (nreduce > 0) { + polygons.splice(0, polygons.length); - for (n = 0; n < arr.length; ++n) { - parts = arr[n]; - if (parts !== undefined) - for (i1 = 0, len = parts.length; i1 < len; ++i1) - if (parts[i1]) polygons.push(parts[i1]); + for (n = 0; n < arr.length; ++n) { + parts = arr[n]; + if (parts !== undefined) + for (i1 = 0, len = parts.length; i1 < len; ++i1) + if (parts[i1]) polygons.push(parts[i1]); + } + + } +}; + +Geometry.prototype.direct_subtract = function (other_tree) { + var a = this.tree, + b = other_tree.tree; + a.invert(); + a.clipTo(b); + b.clipTo(a); + b.invert(); + b.clipTo(a); + b.invert(); + a.build(b.collectPolygons([])); + a.invert(); + return this; +}; + +Geometry.prototype.direct_union = function (other_tree) { + var a = this.tree, + b = other_tree.tree; + + a.clipTo(b); + b.clipTo(a); + b.invert(); + b.clipTo(a); + b.invert(); + a.build(b.collectPolygons([])); + return this; +}; + +Geometry.prototype.direct_intersect = function (other_tree) { + var a = this.tree, + b = other_tree.tree; + + a.invert(); + b.clipTo(a); + b.invert(); + a.clipTo(b); + b.clipTo(a); + a.build(b.collectPolygons([])); + a.invert(); + return this; +}; + +export function CreateNormal(axis_name, pos, size) { + // create geometry to make cut on specified axis + + var vert1, vert2, vert3; + + if (!size || (size < 10000)) size = 10000; + + switch (axis_name) { + case "x": + vert1 = new Vertex(pos, -3 * size, size, 1, 0, 0), + vert3 = new Vertex(pos, size, size, 1, 0, 0), + vert2 = new Vertex(pos, size, -3 * size, 1, 0, 0); + break; + case "y": + vert1 = new Vertex(-3 * size, pos, size, 0, 1, 0), + vert2 = new Vertex(size, pos, size, 0, 1, 0), + vert3 = new Vertex(size, pos, -3 * size, 0, 1, 0); + break; + case "z": + vert1 = new Vertex(-3 * size, size, pos, 0, 0, 1), + vert3 = new Vertex(size, size, pos, 0, 0, 1), + vert2 = new Vertex(size, -3 * size, pos, 0, 0, 1); + break; + } + + var polygon = new Polygon([vert1, vert2, vert3]); + polygon.calculateProperties(); + + var node = new Node([polygon]); + + return new Geometry(node); +} + +Geometry.prototype.cut_from_plane = function (other_tree) { + // just cut peaces from second geometry, which just simple plane + + var a = this.tree, + b = other_tree.tree; + + a.invert(); + b.clipTo(a); + + return this; +}; + + +Geometry.prototype.toGeometry = function () { + var i, j, + matrix = this.matrix ? new THREE.Matrix4().getInverse(this.matrix) : null, + geometry = new THREE.Geometry(), + polygons = this.tree.collectPolygons([]), + polygon_count = polygons.length, + polygon, polygon_vertice_count, + vertice_dict = {}, + vertex_idx_a, vertex_idx_b, vertex_idx_c, + vertex, face; + + for (i = 0; i < polygon_count; ++i) { + polygon = polygons[i]; + polygon_vertice_count = polygon.vertices.length; + + for (j = 2; j < polygon_vertice_count; ++j) { + // verticeUvs = []; + + vertex = polygon.vertices[0]; + // verticeUvs.push( new THREE.Vector2( vertex.uv.x, vertex.uv.y ) ); + vertex = new THREE.Vector3(vertex.x, vertex.y, vertex.z); + if (matrix) vertex.applyMatrix4(matrix); + + if (typeof vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] !== 'undefined') { + vertex_idx_a = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z]; + } else { + geometry.vertices.push(vertex); + vertex_idx_a = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] = geometry.vertices.length - 1; } - } - }; + vertex = polygon.vertices[j - 1]; + // verticeUvs.push( new THREE.Vector2( vertex.uv.x, vertex.uv.y ) ); + vertex = new THREE.Vector3(vertex.x, vertex.y, vertex.z); + if (matrix) vertex.applyMatrix4(matrix); + if (typeof vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] !== 'undefined') { + vertex_idx_b = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z]; + } else { + geometry.vertices.push(vertex); + vertex_idx_b = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] = geometry.vertices.length - 1; + } - ThreeBSP.Geometry.prototype.direct_subtract = function (other_tree) { - var a = this.tree, - b = other_tree.tree; - a.invert(); - a.clipTo(b); - b.clipTo(a); - b.invert(); - b.clipTo(a); - b.invert(); - a.build(b.collectPolygons([])); - a.invert(); - return this; - }; + vertex = polygon.vertices[j]; + // verticeUvs.push( new THREE.Vector2( vertex.uv.x, vertex.uv.y ) ); + vertex = new THREE.Vector3(vertex.x, vertex.y, vertex.z); + if (matrix) vertex.applyMatrix4(matrix); + if (typeof vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] !== 'undefined') { + vertex_idx_c = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z]; + } else { + geometry.vertices.push(vertex); + vertex_idx_c = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] = geometry.vertices.length - 1; + } - ThreeBSP.Geometry.prototype.direct_union = function (other_tree) { - var a = this.tree, - b = other_tree.tree; + face = new THREE.Face3( + vertex_idx_a, + vertex_idx_b, + vertex_idx_c, + new THREE.Vector3(polygon.normal.x, polygon.normal.y, polygon.normal.z) + ); - a.clipTo(b); - b.clipTo(a); - b.invert(); - b.clipTo(a); - b.invert(); - a.build(b.collectPolygons([])); - return this; - }; - - ThreeBSP.Geometry.prototype.direct_intersect = function (other_tree) { - var a = this.tree, - b = other_tree.tree; - - a.invert(); - b.clipTo(a); - b.invert(); - a.clipTo(b); - b.clipTo(a); - a.build(b.collectPolygons([])); - a.invert(); - return this; - }; - - ThreeBSP.CreateNormal = function (axis_name, pos, size) { - // create geometry to make cut on specified axis - - var vert1, vert2, vert3; - - if (!size || (size < 10000)) size = 10000; - - switch (axis_name) { - case "x": - vert1 = new ThreeBSP.Vertex(pos, -3 * size, size, 1, 0, 0), - vert3 = new ThreeBSP.Vertex(pos, size, size, 1, 0, 0), - vert2 = new ThreeBSP.Vertex(pos, size, -3 * size, 1, 0, 0); - break; - case "y": - vert1 = new ThreeBSP.Vertex(-3 * size, pos, size, 0, 1, 0), - vert2 = new ThreeBSP.Vertex(size, pos, size, 0, 1, 0), - vert3 = new ThreeBSP.Vertex(size, pos, -3 * size, 0, 1, 0); - break; - case "z": - vert1 = new ThreeBSP.Vertex(-3 * size, size, pos, 0, 0, 1), - vert3 = new ThreeBSP.Vertex(size, size, pos, 0, 0, 1), - vert2 = new ThreeBSP.Vertex(size, -3 * size, pos, 0, 0, 1); - break; + geometry.faces.push(face); + // geometry.faceVertexUvs[0].push( verticeUvs ); } - var polygon = new ThreeBSP.Polygon([vert1, vert2, vert3]); + } + return geometry; +}; + +Geometry.prototype.scale = function (x, y, z) { + // try to scale as THREE.BufferGeometry + var polygons = this.tree.collectPolygons([]); + + for (var i = 0; i < polygons.length; ++i) { + var polygon = polygons[i]; + for (var k = 0; k < polygon.vertices.length; ++k) { + var v = polygon.vertices[k]; + v.x *= x; + v.y *= y; + v.z *= z; + } + delete polygon.normal; polygon.calculateProperties(); + } +}; - var node = new ThreeBSP.Node([polygon]); +Geometry.prototype.toPolygons = function () { + var polygons = this.tree.collectPolygons([]); - return new ThreeBSP.Geometry(node); - }; + this.tryToCompress(polygons); + for (var i = 0; i < polygons.length; ++i) { + delete polygons[i].id; + delete polygons[i].parent; + } - ThreeBSP.Geometry.prototype.cut_from_plane = function (other_tree) { - // just cut peaces from second geometry, which just simple plane + return polygons; +}; - var a = this.tree, - b = other_tree.tree; +Geometry.prototype.toBufferGeometry = function () { + return CreateBufferGeometry(this.toPolygons()); +}; - a.invert(); - b.clipTo(a); +export function CreateBufferGeometry(polygons) { + var i, j, polygon_count = polygons.length, buf_size = 0; - return this; - }; + for (i = 0; i < polygon_count; ++i) + buf_size += (polygons[i].vertices.length - 2) * 9; + var positions_buf = new Float32Array(buf_size), + normals_buf = new Float32Array(buf_size), + iii = 0, polygon; - ThreeBSP.Geometry.prototype.toGeometry = function () { - var i, j, - matrix = this.matrix ? new THREE.Matrix4().getInverse(this.matrix) : null, - geometry = new THREE.Geometry(), - polygons = this.tree.collectPolygons([]), - polygon_count = polygons.length, - polygon, polygon_vertice_count, - vertice_dict = {}, - vertex_idx_a, vertex_idx_b, vertex_idx_c, - vertex, face; + function CopyVertex(vertex) { - for (i = 0; i < polygon_count; ++i) { - polygon = polygons[i]; - polygon_vertice_count = polygon.vertices.length; + positions_buf[iii] = vertex.x; + positions_buf[iii + 1] = vertex.y; + positions_buf[iii + 2] = vertex.z; - for (j = 2; j < polygon_vertice_count; ++j) { - // verticeUvs = []; - - vertex = polygon.vertices[0]; - // verticeUvs.push( new THREE.Vector2( vertex.uv.x, vertex.uv.y ) ); - vertex = new THREE.Vector3(vertex.x, vertex.y, vertex.z); - if (matrix) vertex.applyMatrix4(matrix); - - if (typeof vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] !== 'undefined') { - vertex_idx_a = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z]; - } else { - geometry.vertices.push(vertex); - vertex_idx_a = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] = geometry.vertices.length - 1; - } - - vertex = polygon.vertices[j - 1]; - // verticeUvs.push( new THREE.Vector2( vertex.uv.x, vertex.uv.y ) ); - vertex = new THREE.Vector3(vertex.x, vertex.y, vertex.z); - if (matrix) vertex.applyMatrix4(matrix); - if (typeof vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] !== 'undefined') { - vertex_idx_b = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z]; - } else { - geometry.vertices.push(vertex); - vertex_idx_b = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] = geometry.vertices.length - 1; - } - - vertex = polygon.vertices[j]; - // verticeUvs.push( new THREE.Vector2( vertex.uv.x, vertex.uv.y ) ); - vertex = new THREE.Vector3(vertex.x, vertex.y, vertex.z); - if (matrix) vertex.applyMatrix4(matrix); - if (typeof vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] !== 'undefined') { - vertex_idx_c = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z]; - } else { - geometry.vertices.push(vertex); - vertex_idx_c = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] = geometry.vertices.length - 1; - } - - face = new THREE.Face3( - vertex_idx_a, - vertex_idx_b, - vertex_idx_c, - new THREE.Vector3(polygon.normal.x, polygon.normal.y, polygon.normal.z) - ); - - geometry.faces.push(face); - // geometry.faceVertexUvs[0].push( verticeUvs ); - } + normals_buf[iii] = polygon.nsign * vertex.nx; + normals_buf[iii + 1] = polygon.nsign * vertex.ny; + normals_buf[iii + 2] = polygon.nsign * vertex.nz; + iii += 3; + } + for (i = 0; i < polygon_count; ++i) { + polygon = polygons[i]; + for (j = 2; j < polygon.vertices.length; ++j) { + CopyVertex(polygon.vertices[0]); + CopyVertex(polygon.vertices[j - 1]); + CopyVertex(polygon.vertices[j]); } - return geometry; - }; + } - ThreeBSP.Geometry.prototype.scale = function (x, y, z) { - // try to scale as THREE.BufferGeometry - var polygons = this.tree.collectPolygons([]); + var geometry = new THREE.BufferGeometry(); + geometry.addAttribute('position', new THREE.BufferAttribute(positions_buf, 3)); + geometry.addAttribute('normal', new THREE.BufferAttribute(normals_buf, 3)); - for (var i = 0; i < polygons.length; ++i) { - var polygon = polygons[i]; - for (var k = 0; k < polygon.vertices.length; ++k) { - var v = polygon.vertices[k]; - v.x *= x; - v.y *= y; - v.z *= z; - } - delete polygon.normal; - polygon.calculateProperties(); - } - }; + // geometry.computeVertexNormals(); + return geometry; +} - ThreeBSP.Geometry.prototype.toPolygons = function () { - var polygons = this.tree.collectPolygons([]); +Geometry.prototype.toMesh = function (material) { + var geometry = this.toGeometry(), + mesh = new THREE.Mesh(geometry, material); - this.tryToCompress(polygons); + if (this.matrix) { + mesh.position.setFromMatrixPosition(this.matrix); + mesh.rotation.setFromRotationMatrix(this.matrix); + } - for (var i = 0; i < polygons.length; ++i) { - delete polygons[i].id; - delete polygons[i].parent; - } + return mesh; +}; - return polygons; - }; - - ThreeBSP.Geometry.prototype.toBufferGeometry = function () { - return ThreeBSP.CreateBufferGeometry(this.toPolygons()); - }; - - ThreeBSP.CreateBufferGeometry = function (polygons) { - var i, j, polygon_count = polygons.length, buf_size = 0; - - for (i = 0; i < polygon_count; ++i) - buf_size += (polygons[i].vertices.length - 2) * 9; - - var positions_buf = new Float32Array(buf_size), - normals_buf = new Float32Array(buf_size), - iii = 0, polygon; - - function CopyVertex(vertex) { - - positions_buf[iii] = vertex.x; - positions_buf[iii + 1] = vertex.y; - positions_buf[iii + 2] = vertex.z; - - normals_buf[iii] = polygon.nsign * vertex.nx; - normals_buf[iii + 1] = polygon.nsign * vertex.ny; - normals_buf[iii + 2] = polygon.nsign * vertex.nz; - iii += 3; - } - - for (i = 0; i < polygon_count; ++i) { - polygon = polygons[i]; - for (j = 2; j < polygon.vertices.length; ++j) { - CopyVertex(polygon.vertices[0]); - CopyVertex(polygon.vertices[j - 1]); - CopyVertex(polygon.vertices[j]); - } - } - - var geometry = new THREE.BufferGeometry(); - geometry.addAttribute('position', new THREE.BufferAttribute(positions_buf, 3)); - geometry.addAttribute('normal', new THREE.BufferAttribute(normals_buf, 3)); - - // geometry.computeVertexNormals(); - return geometry; - }; - - ThreeBSP.Geometry.prototype.toMesh = function (material) { - var geometry = this.toGeometry(), - mesh = new THREE.Mesh(geometry, material); - - if (this.matrix) { - mesh.position.setFromMatrixPosition(this.matrix); - mesh.rotation.setFromRotationMatrix(this.matrix); - } - - return mesh; - }; - - ThreeBSP.Polygon = function (vertices, normal, w) { +export class Polygon { + constructor(vertices, normal, w) { if (!(vertices instanceof Array)) { vertices = []; } @@ -511,141 +471,144 @@ this.normal = this.w = undefined; } }; +} - ThreeBSP.Polygon.prototype.copyProperties = function (parent, more) { - this.normal = parent.normal; // .clone(); - this.w = parent.w; - this.nsign = parent.nsign; - if (more && (parent.id !== undefined)) { - this.id = parent.id; - this.parent = parent; + +Polygon.prototype.copyProperties = function (parent, more) { + this.normal = parent.normal; // .clone(); + this.w = parent.w; + this.nsign = parent.nsign; + if (more && (parent.id !== undefined)) { + this.id = parent.id; + this.parent = parent; + } + return this; +}; + +Polygon.prototype.calculateProperties = function () { + if (this.normal) return; + + var a = this.vertices[0], + b = this.vertices[1], + c = this.vertices[2]; + + this.nsign = 1; + + this.normal = b.clone().subtract(a).cross( + c.clone().subtract(a) + ).normalize(); + + this.w = this.normal.clone().dot(a); + return this; +}; + +Polygon.prototype.clone = function () { + var vertice_count = this.vertices.length, + polygon = new Polygon; + + for (var i = 0; i < vertice_count; ++i) + polygon.vertices.push(this.vertices[i].clone()); + + return polygon.copyProperties(this); +}; + +Polygon.prototype.flip = function () { + + /// normal is not changed, only sign variable + //this.normal.multiplyScalar( -1 ); + //this.w *= -1; + + this.nsign *= -1; + + this.vertices.reverse(); + + return this; +}; + +Polygon.prototype.classifyVertex = function (vertex) { + var side_value = this.nsign * (this.normal.dot(vertex) - this.w); + + if (side_value < -EPSILON) return BACK; + if (side_value > EPSILON) return FRONT; + return COPLANAR; +}; + +Polygon.prototype.classifySide = function (polygon) { + var i, classification, + num_positive = 0, num_negative = 0, + vertice_count = polygon.vertices.length; + + for (i = 0; i < vertice_count; ++i) { + classification = this.classifyVertex(polygon.vertices[i]); + if (classification === FRONT) { + ++num_positive; + } else if (classification === BACK) { + ++num_negative; } - return this; - }; + } - ThreeBSP.Polygon.prototype.calculateProperties = function () { - if (this.normal) return; + if (num_positive > 0 && num_negative === 0) return FRONT; + if (num_positive === 0 && num_negative > 0) return BACK; + if (num_positive === 0 && num_negative === 0) return COPLANAR; + return SPANNING; +}; - var a = this.vertices[0], - b = this.vertices[1], - c = this.vertices[2]; +Polygon.prototype.splitPolygon = function (polygon, coplanar_front, coplanar_back, front, back) { + var classification = this.classifySide(polygon); - this.nsign = 1; + if (classification === COPLANAR) { - this.normal = b.clone().subtract(a).cross( - c.clone().subtract(a) - ).normalize(); + ((this.nsign * polygon.nsign * this.normal.dot(polygon.normal) > 0) ? coplanar_front : coplanar_back).push(polygon); - this.w = this.normal.clone().dot(a); - return this; - }; + } else if (classification === FRONT) { - ThreeBSP.Polygon.prototype.clone = function () { - var vertice_count = this.vertices.length, - polygon = new ThreeBSP.Polygon; + front.push(polygon); - for (var i = 0; i < vertice_count; ++i) - polygon.vertices.push(this.vertices[i].clone()); + } else if (classification === BACK) { - return polygon.copyProperties(this); - }; + back.push(polygon); - ThreeBSP.Polygon.prototype.flip = function () { + } else { - /// normal is not changed, only sign variable - //this.normal.multiplyScalar( -1 ); - //this.w *= -1; - - this.nsign *= -1; - - this.vertices.reverse(); - - return this; - }; - - ThreeBSP.Polygon.prototype.classifyVertex = function (vertex) { - var side_value = this.nsign * (this.normal.dot(vertex) - this.w); - - if (side_value < -EPSILON) return BACK; - if (side_value > EPSILON) return FRONT; - return COPLANAR; - }; - - ThreeBSP.Polygon.prototype.classifySide = function (polygon) { - var i, classification, - num_positive = 0, num_negative = 0, - vertice_count = polygon.vertices.length; + var vertice_count = polygon.vertices.length, + nnx = this.normal.x, + nny = this.normal.y, + nnz = this.normal.z, + i, j, ti, tj, vi, vj, + t, v, + f = [], b = []; for (i = 0; i < vertice_count; ++i) { - classification = this.classifyVertex(polygon.vertices[i]); - if (classification === FRONT) { - ++num_positive; - } else if (classification === BACK) { - ++num_negative; + + j = (i + 1) % vertice_count; + vi = polygon.vertices[i]; + vj = polygon.vertices[j]; + ti = this.classifyVertex(vi); + tj = this.classifyVertex(vj); + + if (ti != BACK) f.push(vi); + if (ti != FRONT) b.push(vi); + if ((ti | tj) === SPANNING) { + // t = ( this.w - this.normal.dot( vi ) ) / this.normal.dot( vj.clone().subtract( vi ) ); + //v = vi.clone().lerp( vj, t ); + + t = (this.w - (nnx * vi.x + nny * vi.y + nnz * vi.z)) / (nnx * (vj.x - vi.x) + nny * (vj.y - vi.y) + nnz * (vj.z - vi.z)); + + v = vi.interpolate(vj, t); + f.push(v); + b.push(v); } } - if (num_positive > 0 && num_negative === 0) return FRONT; - if (num_positive === 0 && num_negative > 0) return BACK; - if (num_positive === 0 && num_negative === 0) return COPLANAR; - return SPANNING; - }; + //if ( f.length >= 3 ) front.push( new Polygon( f ).calculateProperties() ); + //if ( b.length >= 3 ) back.push( new Polygon( b ).calculateProperties() ); + if (f.length >= 3) front.push(new Polygon(f).copyProperties(polygon, true)); + if (b.length >= 3) back.push(new Polygon(b).copyProperties(polygon, true)); + } +}; - ThreeBSP.Polygon.prototype.splitPolygon = function (polygon, coplanar_front, coplanar_back, front, back) { - var classification = this.classifySide(polygon); - - if (classification === COPLANAR) { - - ((this.nsign * polygon.nsign * this.normal.dot(polygon.normal) > 0) ? coplanar_front : coplanar_back).push(polygon); - - } else if (classification === FRONT) { - - front.push(polygon); - - } else if (classification === BACK) { - - back.push(polygon); - - } else { - - var vertice_count = polygon.vertices.length, - nnx = this.normal.x, - nny = this.normal.y, - nnz = this.normal.z, - i, j, ti, tj, vi, vj, - t, v, - f = [], b = []; - - for (i = 0; i < vertice_count; ++i) { - - j = (i + 1) % vertice_count; - vi = polygon.vertices[i]; - vj = polygon.vertices[j]; - ti = this.classifyVertex(vi); - tj = this.classifyVertex(vj); - - if (ti != BACK) f.push(vi); - if (ti != FRONT) b.push(vi); - if ((ti | tj) === SPANNING) { - // t = ( this.w - this.normal.dot( vi ) ) / this.normal.dot( vj.clone().subtract( vi ) ); - //v = vi.clone().lerp( vj, t ); - - t = (this.w - (nnx * vi.x + nny * vi.y + nnz * vi.z)) / (nnx * (vj.x - vi.x) + nny * (vj.y - vi.y) + nnz * (vj.z - vi.z)); - - v = vi.interpolate(vj, t); - f.push(v); - b.push(v); - } - } - - //if ( f.length >= 3 ) front.push( new ThreeBSP.Polygon( f ).calculateProperties() ); - //if ( b.length >= 3 ) back.push( new ThreeBSP.Polygon( b ).calculateProperties() ); - if (f.length >= 3) front.push(new ThreeBSP.Polygon(f).copyProperties(polygon, true)); - if (b.length >= 3) back.push(new ThreeBSP.Polygon(b).copyProperties(polygon, true)); - } - }; - - ThreeBSP.Vertex = function (x, y, z, nx, ny, nz) { +export class Vertex { + constructor(x, y, z, nx, ny, nz) { this.x = x; this.y = y; this.z = z; @@ -653,124 +616,126 @@ this.ny = ny; this.nz = nz; }; +} - ThreeBSP.Vertex.prototype.setnormal = function (nx, ny, nz) { - this.nx = nx; - this.ny = ny; - this.nz = nz; - }; +Vertex.prototype.setnormal = function (nx, ny, nz) { + this.nx = nx; + this.ny = ny; + this.nz = nz; +}; - ThreeBSP.Vertex.prototype.clone = function () { - return new ThreeBSP.Vertex(this.x, this.y, this.z, this.nx, this.ny, this.nz); - }; +Vertex.prototype.clone = function () { + return new Vertex(this.x, this.y, this.z, this.nx, this.ny, this.nz); +}; - ThreeBSP.Vertex.prototype.add = function (vertex) { - this.x += vertex.x; - this.y += vertex.y; - this.z += vertex.z; - return this; - }; +Vertex.prototype.add = function (vertex) { + this.x += vertex.x; + this.y += vertex.y; + this.z += vertex.z; + return this; +}; - ThreeBSP.Vertex.prototype.subtract = function (vertex) { - this.x -= vertex.x; - this.y -= vertex.y; - this.z -= vertex.z; - return this; - }; +Vertex.prototype.subtract = function (vertex) { + this.x -= vertex.x; + this.y -= vertex.y; + this.z -= vertex.z; + return this; +}; - ThreeBSP.Vertex.prototype.multiplyScalar = function (scalar) { - this.x *= scalar; - this.y *= scalar; - this.z *= scalar; - return this; - }; +Vertex.prototype.multiplyScalar = function (scalar) { + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; + return this; +}; - ThreeBSP.Vertex.prototype.cross = function (vertex) { - var x = this.x, - y = this.y, - z = this.z; +Vertex.prototype.cross = function (vertex) { + var x = this.x, + y = this.y, + z = this.z; - this.x = y * vertex.z - z * vertex.y; - this.y = z * vertex.x - x * vertex.z; - this.z = x * vertex.y - y * vertex.x; + this.x = y * vertex.z - z * vertex.y; + this.y = z * vertex.x - x * vertex.z; + this.z = x * vertex.y - y * vertex.x; - return this; - }; + return this; +}; - ThreeBSP.Vertex.prototype.normalize = function () { - var length = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); +Vertex.prototype.normalize = function () { + var length = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); - this.x /= length; - this.y /= length; - this.z /= length; + this.x /= length; + this.y /= length; + this.z /= length; - return this; - }; + return this; +}; - ThreeBSP.Vertex.prototype.dot = function (vertex) { - return this.x * vertex.x + this.y * vertex.y + this.z * vertex.z; - }; +Vertex.prototype.dot = function (vertex) { + return this.x * vertex.x + this.y * vertex.y + this.z * vertex.z; +}; - ThreeBSP.Vertex.prototype.diff = function (vertex) { - var dx = (this.x - vertex.x), - dy = (this.y - vertex.y), - dz = (this.z - vertex.z), - len2 = this.x * this.x + this.y * this.y + this.z * this.z; +Vertex.prototype.diff = function (vertex) { + var dx = (this.x - vertex.x), + dy = (this.y - vertex.y), + dz = (this.z - vertex.z), + len2 = this.x * this.x + this.y * this.y + this.z * this.z; - return (dx * dx + dy * dy + dz * dz) / (len2 > 0 ? len2 : 1e-10); - }; + return (dx * dx + dy * dy + dz * dz) / (len2 > 0 ? len2 : 1e-10); +}; - /* - ThreeBSP.Vertex.prototype.lerp = function( a, t ) { - this.add( - a.clone().subtract( this ).multiplyScalar( t ) - ); +/* + Vertex.prototype.lerp = function( a, t ) { + this.add( + a.clone().subtract( this ).multiplyScalar( t ) + ); - this.normal.add( - a.normal.clone().sub( this.normal ).multiplyScalar( t ) - ); + this.normal.add( + a.normal.clone().sub( this.normal ).multiplyScalar( t ) + ); - //this.uv.add( - // a.uv.clone().sub( this.uv ).multiplyScalar( t ) - //); + //this.uv.add( + // a.uv.clone().sub( this.uv ).multiplyScalar( t ) + //); - return this; - }; - ThreeBSP.Vertex.prototype.interpolate = function( other, t ) { - return this.clone().lerp( other, t ); - }; - */ + return this; + }; + Vertex.prototype.interpolate = function( other, t ) { + return this.clone().lerp( other, t ); + }; +*/ - ThreeBSP.Vertex.prototype.interpolate = function (a, t) { - var t1 = 1 - t; - return new ThreeBSP.Vertex(this.x * t1 + a.x * t, this.y * t1 + a.y * t, this.z * t1 + a.z * t, - this.nx * t1 + a.nx * t, this.ny * t1 + a.ny * t, this.nz * t1 + a.nz * t); - }; +Vertex.prototype.interpolate = function (a, t) { + var t1 = 1 - t; + return new Vertex(this.x * t1 + a.x * t, this.y * t1 + a.y * t, this.z * t1 + a.z * t, + this.nx * t1 + a.nx * t, this.ny * t1 + a.ny * t, this.nz * t1 + a.nz * t); +}; - ThreeBSP.Vertex.prototype.applyMatrix4 = function (m) { +Vertex.prototype.applyMatrix4 = function (m) { - // input: THREE.Matrix4 affine matrix + // input: THREE.Matrix4 affine matrix - var x = this.x, y = this.y, z = this.z, e = m.elements; + var x = this.x, y = this.y, z = this.z, e = m.elements; - this.x = e[0] * x + e[4] * y + e[8] * z + e[12]; - this.y = e[1] * x + e[5] * y + e[9] * z + e[13]; - this.z = e[2] * x + e[6] * y + e[10] * z + e[14]; + this.x = e[0] * x + e[4] * y + e[8] * z + e[12]; + this.y = e[1] * x + e[5] * y + e[9] * z + e[13]; + this.z = e[2] * x + e[6] * y + e[10] * z + e[14]; - x = this.nx; - y = this.ny; - z = this.nz; + x = this.nx; + y = this.ny; + z = this.nz; - this.nx = e[0] * x + e[4] * y + e[8] * z; - this.ny = e[1] * x + e[5] * y + e[9] * z; - this.nz = e[2] * x + e[6] * y + e[10] * z; + this.nx = e[0] * x + e[4] * y + e[8] * z; + this.ny = e[1] * x + e[5] * y + e[9] * z; + this.nz = e[2] * x + e[6] * y + e[10] * z; - return this; - }; + return this; +}; - // ================================================================================================ +// ================================================================================================ - ThreeBSP.Node = function (polygons, nodeid) { +export class Node { + constructor(polygons, nodeid) { this.polygons = []; this.front = this.back = undefined; @@ -793,116 +758,112 @@ if (nodeid !== undefined) this.maxnodeid = nodeid; if (front.length > 0) - this.front = new ThreeBSP.Node(front); + this.front = new Node(front); if (back.length > 0) - this.back = new ThreeBSP.Node(back); + this.back = new Node(back); }; +} - ThreeBSP.Node.isConvex = function (polygons) { - var i, j, len = polygons.length; - for (i = 0; i < len; ++i) - for (j = 0; j < len; ++j) - if (i !== j && polygons[i].classifySide(polygons[j]) !== BACK) return false; - return true; - }; +Node.isConvex = function (polygons) { + var i, j, len = polygons.length; + for (i = 0; i < len; ++i) + for (j = 0; j < len; ++j) + if (i !== j && polygons[i].classifySide(polygons[j]) !== BACK) return false; + return true; +}; - ThreeBSP.Node.prototype.build = function (polygons) { - var polygon_count = polygons.length, - front = [], back = []; +Node.prototype.build = function (polygons) { + var polygon_count = polygons.length, + front = [], back = []; - if (!this.divider) - this.divider = polygons[0].clone(); + if (!this.divider) + this.divider = polygons[0].clone(); - for (var i = 0; i < polygon_count; ++i) - this.divider.splitPolygon(polygons[i], this.polygons, this.polygons, front, back); + for (var i = 0; i < polygon_count; ++i) + this.divider.splitPolygon(polygons[i], this.polygons, this.polygons, front, back); - if (front.length > 0) { - if (!this.front) this.front = new ThreeBSP.Node(); - this.front.build(front); - } + if (front.length > 0) { + if (!this.front) this.front = new Node(); + this.front.build(front); + } - if (back.length > 0) { - if (!this.back) this.back = new ThreeBSP.Node(); - this.back.build(back); - } - }; + if (back.length > 0) { + if (!this.back) this.back = new Node(); + this.back.build(back); + } +}; - ThreeBSP.Node.prototype.collectPolygons = function (arr) { - var len = this.polygons.length; - for (var i = 0; i < len; ++i) arr.push(this.polygons[i]); - if (this.front) this.front.collectPolygons(arr); - if (this.back) this.back.collectPolygons(arr); - return arr; - }; +Node.prototype.collectPolygons = function (arr) { + var len = this.polygons.length; + for (var i = 0; i < len; ++i) arr.push(this.polygons[i]); + if (this.front) this.front.collectPolygons(arr); + if (this.back) this.back.collectPolygons(arr); + return arr; +}; - ThreeBSP.Node.prototype.allPolygons = function () { - var polygons = this.polygons.slice(); - if (this.front) polygons = polygons.concat(this.front.allPolygons()); - if (this.back) polygons = polygons.concat(this.back.allPolygons()); - return polygons; - }; +Node.prototype.allPolygons = function () { + var polygons = this.polygons.slice(); + if (this.front) polygons = polygons.concat(this.front.allPolygons()); + if (this.back) polygons = polygons.concat(this.back.allPolygons()); + return polygons; +}; - ThreeBSP.Node.prototype.numPolygons = function () { - var res = this.polygons.length; - if (this.front) res += this.front.numPolygons(); - if (this.back) res += this.back.numPolygons(); - return res; - }; +Node.prototype.numPolygons = function () { + var res = this.polygons.length; + if (this.front) res += this.front.numPolygons(); + if (this.back) res += this.back.numPolygons(); + return res; +}; - ThreeBSP.Node.prototype.clone = function () { - var node = new ThreeBSP.Node(); +Node.prototype.clone = function () { + var node = new Node(); - node.divider = this.divider.clone(); - node.polygons = this.polygons.map(function (polygon) { - return polygon.clone(); - }); - node.front = this.front && this.front.clone(); - node.back = this.back && this.back.clone(); + node.divider = this.divider.clone(); + node.polygons = this.polygons.map(function (polygon) { + return polygon.clone(); + }); + node.front = this.front && this.front.clone(); + node.back = this.back && this.back.clone(); - return node; - }; + return node; +}; - ThreeBSP.Node.prototype.invert = function () { - var polygon_count = this.polygons.length; +Node.prototype.invert = function () { + var polygon_count = this.polygons.length; - for (var i = 0; i < polygon_count; ++i) - this.polygons[i].flip(); + for (var i = 0; i < polygon_count; ++i) + this.polygons[i].flip(); - this.divider.flip(); - if (this.front) this.front.invert(); - if (this.back) this.back.invert(); + this.divider.flip(); + if (this.front) this.front.invert(); + if (this.back) this.back.invert(); - var temp = this.front; - this.front = this.back; - this.back = temp; + var temp = this.front; + this.front = this.back; + this.back = temp; - return this; - }; + return this; +}; - ThreeBSP.Node.prototype.clipPolygons = function (polygons) { +Node.prototype.clipPolygons = function (polygons) { - if (!this.divider) return polygons.slice(); + if (!this.divider) return polygons.slice(); - var polygon_count = polygons.length, front = [], back = []; + var polygon_count = polygons.length, front = [], back = []; - for (var i = 0; i < polygon_count; ++i) - this.divider.splitPolygon(polygons[i], front, back, front, back); + for (var i = 0; i < polygon_count; ++i) + this.divider.splitPolygon(polygons[i], front, back, front, back); - if (this.front) front = this.front.clipPolygons(front); - if (this.back) back = this.back.clipPolygons(back); - else back = []; + if (this.front) front = this.front.clipPolygons(front); + if (this.back) back = this.back.clipPolygons(back); + else back = []; - return front.concat(back); - }; - - ThreeBSP.Node.prototype.clipTo = function (node) { - this.polygons = node.clipPolygons(this.polygons); - if (this.front) this.front.clipTo(node); - if (this.back) this.back.clipTo(node); - }; - - return ThreeBSP; - -})); + return front.concat(back); +}; +Node.prototype.clipTo = function (node) { + this.polygons = node.clipPolygons(this.polygons); + if (this.front) this.front.clipTo(node); + if (this.back) this.back.clipTo(node); +}; \ No newline at end of file